From 8b60eb6961ca4875f7d73c33b892e6322ccf70cb Mon Sep 17 00:00:00 2001
From: TzeYiing
Date: Tue, 3 Sep 2024 03:27:39 +0800
Subject: [PATCH 1/4] feat: implement query page
---
assets/css/app.scss | 2 +
assets/js/app.js | 5 +
assets/js/interfaces/DataTable.jsx | 13 +
assets/js/interfaces/index.js | 1 +
assets/package-lock.json | 104 +++++---
assets/package.json | 3 +-
lib/logflare_web/live/query_live.ex | 242 ++++++++++++++++++
lib/logflare_web/router.ex | 6 +
.../templates/source/dashboard.html.heex | 5 +-
mix.exs | 3 +-
mix.lock | 1 +
.../live_views/query_live_test.exs | 43 ++++
12 files changed, 396 insertions(+), 32 deletions(-)
create mode 100644 assets/js/interfaces/DataTable.jsx
create mode 100644 lib/logflare_web/live/query_live.ex
create mode 100644 test/logflare_web/live_views/query_live_test.exs
diff --git a/assets/css/app.scss b/assets/css/app.scss
index ebc81ae59..9ad737513 100644
--- a/assets/css/app.scss
+++ b/assets/css/app.scss
@@ -1,5 +1,7 @@
/* This file is for your main application css. */
@import "~bootstrap/scss/bootstrap";
+@import "../../deps/live_monaco_editor/priv/static/live_monaco_editor.min.css";
+
@import "named_variables";
@import "dashboard";
@import "header";
diff --git a/assets/js/app.js b/assets/js/app.js
index 7b7484ed3..1e6d0579e 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,6 +1,7 @@
import "../css/app.scss";
import { Socket } from "phoenix";
import "../css/tailwind.css";
+
import "bootstrap";
import ClipboardJS from "clipboard";
import * as Dashboard from "./dashboard";
@@ -19,6 +20,8 @@ import sourceLiveViewHooks from "./source_lv_hooks";
import logsLiveViewHooks from "./log_event_live_hooks";
import $ from "jquery";
import moment from "moment";
+import { CodeEditorHook } from "../../deps/live_monaco_editor/priv/static/live_monaco_editor.esm"
+
// set moment globally before daterangepicker
window.moment = moment;
@@ -50,6 +53,8 @@ const hooks = {
...logsLiveViewHooks,
...LiveModalHooks,
...BillingHooks,
+ CodeEditorHook
+
};
let liveSocket = new LiveSocket("/live", Socket, {
diff --git a/assets/js/interfaces/DataTable.jsx b/assets/js/interfaces/DataTable.jsx
new file mode 100644
index 000000000..29a62f984
--- /dev/null
+++ b/assets/js/interfaces/DataTable.jsx
@@ -0,0 +1,13 @@
+
+import DataGrid from 'react-data-grid';
+
+function DataTable({columns, rows} = props) {
+ console.log(props)
+ return rows[i]}
+ rowsCount={3}
+ minHeight={150}
+ />;
+}
+
+export default DataTable
\ No newline at end of file
diff --git a/assets/js/interfaces/index.js b/assets/js/interfaces/index.js
index c13eaff46..2c3d0d294 100644
--- a/assets/js/interfaces/index.js
+++ b/assets/js/interfaces/index.js
@@ -1,3 +1,4 @@
export {default as EndpointEditor} from "./EndpointEditor.jsx"
export {default as EndpointsBrowserList} from "./EndpointsBrowserList.jsx"
export {default as ShowEndpoint} from "./ShowEndpoint.jsx"
+export {default as DataTable} from "./DataTable.jsx"
diff --git a/assets/package-lock.json b/assets/package-lock.json
index f0bd55d4a..26067f88f 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -26,6 +26,7 @@
"phoenix_live_view": "file:../deps/phoenix_live_view",
"react": "^16.11.0",
"react-bootstrap": "^1.6.6",
+ "react-data-grid": "^6.1.0",
"react-dom": "^16.11.0",
"react-spinners": "^0.9.0",
"sql-formatter": "^2.3.3"
@@ -38,20 +39,23 @@
"glob": "^10.3.1",
"postcss": "^8.4.31",
"sass": "^1.58.3",
- "tailwindcss": "^3.3.2"
+ "tailwindcss": "^3.4.10"
}
},
"../deps/phoenix": {
- "version": "0.0.1"
+ "version": "1.7.10",
+ "license": "MIT"
},
"../deps/phoenix_html": {
- "version": "0.0.1"
+ "version": "3.3.3"
},
"../deps/phoenix_live_react": {
- "version": "0.0.1"
+ "version": "0.4.2",
+ "license": "MIT"
},
"../deps/phoenix_live_view": {
- "version": "0.0.1"
+ "version": "0.20.1",
+ "license": "MIT"
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
@@ -1685,9 +1689,9 @@
}
},
"node_modules/fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -2046,9 +2050,9 @@
}
},
"node_modules/jiti": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
- "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+ "version": "1.21.6",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"bin": {
"jiti": "bin/jiti.js"
@@ -2643,6 +2647,20 @@
"react-dom": ">=16.8.0"
}
},
+ "node_modules/react-data-grid": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-6.1.0.tgz",
+ "integrity": "sha512-N1UtiHvsowEPzhx0VPqQKvGgSza/YNljczbisFDGMjawiGApS2taMv7h+EDXDx49CdaA6ur4eYS0z10x63IUpw==",
+ "dependencies": {
+ "object-assign": "^4.1.1",
+ "react-is-deprecated": "^0.1.2",
+ "shallowequal": "^1.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0",
+ "react-dom": "^16.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
@@ -2662,6 +2680,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-is-deprecated": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/react-is-deprecated/-/react-is-deprecated-0.1.2.tgz",
+ "integrity": "sha512-n3Y04lqbuwMiSywwAKBwW89YxAPuFwS5tYA4L6wDGLQCdSsod1KSfzCIiTTUvS9hPdaB39HdvxjxAaS0Lk4h+A=="
+ },
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
@@ -2827,6 +2850,11 @@
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3078,9 +3106,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
- "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
+ "version": "3.4.10",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
+ "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
"dev": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@@ -3088,10 +3116,10 @@
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
- "fast-glob": "^3.2.12",
+ "fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "jiti": "^1.18.2",
+ "jiti": "^1.21.0",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@@ -3103,7 +3131,6 @@
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
- "postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
@@ -4517,9 +4544,9 @@
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
},
"fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@@ -4787,9 +4814,9 @@
}
},
"jiti": {
- "version": "1.18.2",
- "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
- "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
+ "version": "1.21.6",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
+ "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true
},
"jquery": {
@@ -5190,6 +5217,16 @@
"warning": "^4.0.3"
}
},
+ "react-data-grid": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-6.1.0.tgz",
+ "integrity": "sha512-N1UtiHvsowEPzhx0VPqQKvGgSza/YNljczbisFDGMjawiGApS2taMv7h+EDXDx49CdaA6ur4eYS0z10x63IUpw==",
+ "requires": {
+ "object-assign": "^4.1.1",
+ "react-is-deprecated": "^0.1.2",
+ "shallowequal": "^1.1.0"
+ }
+ },
"react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
@@ -5206,6 +5243,11 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-is-deprecated": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/react-is-deprecated/-/react-is-deprecated-0.1.2.tgz",
+ "integrity": "sha512-n3Y04lqbuwMiSywwAKBwW89YxAPuFwS5tYA4L6wDGLQCdSsod1KSfzCIiTTUvS9hPdaB39HdvxjxAaS0Lk4h+A=="
+ },
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
@@ -5323,6 +5365,11 @@
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -5505,9 +5552,9 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"tailwindcss": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
- "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
+ "version": "3.4.10",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
+ "integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
"dev": true,
"requires": {
"@alloc/quick-lru": "^5.2.0",
@@ -5515,10 +5562,10 @@
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
- "fast-glob": "^3.2.12",
+ "fast-glob": "^3.3.0",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "jiti": "^1.18.2",
+ "jiti": "^1.21.0",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
@@ -5530,7 +5577,6 @@
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
- "postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
diff --git a/assets/package.json b/assets/package.json
index 7e99b6b64..d79e26c3f 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -26,6 +26,7 @@
"phoenix_live_view": "file:../deps/phoenix_live_view",
"react": "^16.11.0",
"react-bootstrap": "^1.6.6",
+ "react-data-grid": "^6.1.0",
"react-dom": "^16.11.0",
"react-spinners": "^0.9.0",
"sql-formatter": "^2.3.3"
@@ -38,6 +39,6 @@
"glob": "^10.3.1",
"postcss": "^8.4.31",
"sass": "^1.58.3",
- "tailwindcss": "^3.3.2"
+ "tailwindcss": "^3.4.10"
}
}
diff --git a/lib/logflare_web/live/query_live.ex b/lib/logflare_web/live/query_live.ex
new file mode 100644
index 000000000..a529c00f7
--- /dev/null
+++ b/lib/logflare_web/live/query_live.ex
@@ -0,0 +1,242 @@
+defmodule LogflareWeb.QueryLive do
+ @moduledoc false
+ use LogflareWeb, :live_view
+ use Phoenix.Component
+
+ require Logger
+
+ alias Logflare.Endpoints
+ alias Logflare.Users
+
+ def render(assigns) do
+ ~H"""
+ <.subheader>
+ <:path>
+ ~/<.subheader_path_link live_patch to={~p"/query"}>query
+
+
+
+
+
+ Query your data with BigQuery SQL directly. You can refer to source names directly in your SELECT queries, for example
+ SELECT datetime(timestamp) as timestamp, event_message, metadata from `MyApp.Logs`
+ Some pointers:
+
+ Always have a filter over the timestamp
column in your WHERE
clause
+ Use CROSS JOIN UNNEST(my_table.my_column) as col
to use nested fields in your query
+ Smaller time ranges load faster
+
+ Read the docs
+ to find out more about querying Logflare with BigQuery SQL
+
+
+
+ <.form for={%{}} phx-submit="run-query" class="tw-min-h-[80px] tw-flex tw-flex-col tw-gap-4">
+ "on",
+ "language" => "sql",
+ "fontSize" => 12,
+ "padding" => %{
+ "top" => 14
+ },
+ "fixedOverflowWidgets" => false,
+ "contextmenu" => false,
+ "hideCursorInOverviewRuler" => true,
+ "smoothScrolling" => true,
+ "scrollbar" => %{
+ "vertical" => "auto",
+ "horizontal" => "hidden",
+ "verticalScrollbarSize" => 6
+ },
+ "lineNumbers" => "off",
+ "glyphMargin" => false,
+ "lineNumbersMinChars" => 0,
+ "folding" => false,
+ "roundedSelection" => true,
+ "editorClassName" => "",
+ "minimap" => %{
+ "enabled" => false
+ },
+ "placeholder" => "SELECT timestamp, event_message from `MyApp.Source`"
+ }
+ )
+ }
+ />
+
+ <%= submit("Run query", class: "btn btn-secondary") %>
+
+
+
+
+ <.alert variant="warning">
+ SQL Parse error!
+
+ <%= @parse_error_message %>
+
+
+
+
+
+ Query result
+
+ No rows returned from query. Try adjusting your query and try again!
+
+
+ <% keys = Map.keys(hd(@query_result_rows)) |> Enum.sort() %>
+
+
+
+ <%= k %>
+
+
+
+
+
+ <%= case value = Map.get(row, k) do %>
+ <% value when is_map(value) or is_list(value) -> %>
+
+ <%= Jason.encode!(value) |> String.slice(0..150) %>
+
+ <% value -> %>
+
+ <%= value %>
+
+ <% end %>
+
+
+
+
+
+
+
<%= Jason.encode!(value, pretty: true) %>
+
+
+
+
+
+
+
+
+
+
+
+ """
+ end
+
+ def mount(%{}, %{"user_id" => user_id} = params, socket) do
+ user = Users.get(user_id)
+
+ query_string =
+ Map.get(
+ params,
+ "q",
+ "SELECT id, timestamp, metadata, event_message \nFROM `YourSource` \nWHERE timestamp > '#{DateTime.utc_now() |> DateTime.to_iso8601()}'"
+ )
+
+ socket =
+ socket
+ |> assign(:user_id, user_id)
+ |> assign(:user, user)
+ |> assign(:query_result_rows, nil)
+ |> assign(:parse_error_message, nil)
+ |> assign(:query_string, query_string)
+
+ {:ok, socket}
+ end
+
+ def handle_params(params, _uri, socket) do
+ query_string = params["q"] || socket.assigns.query_string
+
+ socket =
+ socket
+ |> assign(:query_string, query_string)
+ |> then(fn
+ socket when query_string != "" ->
+ case Endpoints.parse_query_string(query_string) do
+ {:ok, _} ->
+ socket
+
+ {:error, err} ->
+ assign(
+ socket,
+ :parse_error_message,
+ if(is_binary(err), do: err, else: inspect(err))
+ )
+ |> assign(:query_result_rows, nil)
+ end
+
+ socket ->
+ socket
+ end)
+
+ {:noreply, socket}
+ end
+
+ def handle_event(
+ "run-query",
+ %{"live_monaco_editor" => %{"query" => query_string}},
+ %{assigns: %{user: user}} = socket
+ ) do
+ socket =
+ run_query(socket, user, query_string)
+ |> push_patch(to: ~p"/query?#{%{q: query_string}}")
+
+ {:noreply, socket}
+ end
+
+ def handle_event(
+ "parse-query",
+ %{"value" => query_string},
+ socket
+ ) do
+ socket =
+ case Endpoints.parse_query_string(query_string) do
+ {:ok, _} ->
+ socket
+ |> assign(:parse_error_message, nil)
+
+ {:error, err} ->
+ error = if(is_binary(err), do: err, else: inspect(err))
+
+ socket
+ |> assign(:parse_error_message, error)
+ end
+ |> assign(:query_string, query_string)
+
+ {:noreply, socket}
+ end
+
+ def handle_event("parse-query", %{"_target" => ["live_monaco_editor", _]}, socket) do
+ # ignore change events from the editor field
+ {:noreply, socket}
+ end
+
+ defp run_query(socket, user, query_string) do
+ case Endpoints.run_query_string(user, {:bq_sql, query_string}, params: %{}) do
+ {:ok, %{rows: rows}} ->
+ socket
+ |> put_flash(:info, "Ran query successfully")
+ |> assign(:query_result_rows, rows)
+
+ {:error, err} ->
+ socket
+ |> put_flash(:error, "Error occured when running query: #{inspect(err)}")
+ end
+ end
+end
diff --git a/lib/logflare_web/router.ex b/lib/logflare_web/router.ex
index e3830c10d..4b623311c 100644
--- a/lib/logflare_web/router.ex
+++ b/lib/logflare_web/router.ex
@@ -176,6 +176,12 @@ defmodule LogflareWeb.Router do
live("/backends/:id/edit", BackendsLive, :edit)
end
+ scope "/query", LogflareWeb do
+ pipe_through([:browser, :require_auth])
+
+ live("/", QueryLive, :index)
+ end
+
scope "/endpoints", LogflareWeb do
pipe_through([:browser, :require_auth])
diff --git a/lib/logflare_web/templates/source/dashboard.html.heex b/lib/logflare_web/templates/source/dashboard.html.heex
index 56a2fa263..d17d6ba13 100644
--- a/lib/logflare_web/templates/source/dashboard.html.heex
+++ b/lib/logflare_web/templates/source/dashboard.html.heex
@@ -87,7 +87,10 @@
- <.link href={~p"/sources/new"} class="btn btn-primary btn-sm" id="new-source-button">
+ <.link href={~p"/query"} class="btn btn-primary btn-sm">
+ Run a query
+
+ <.link href={~p"/sources/new"} class="btn btn-primary btn-sm">
New source
diff --git a/mix.exs b/mix.exs
index 91c15b993..44a31629e 100644
--- a/mix.exs
+++ b/mix.exs
@@ -225,7 +225,8 @@ defmodule Logflare.Mixfile do
{:opentelemetry_api, "~> 1.2"},
{:opentelemetry_exporter, "~> 1.6"},
{:opentelemetry_phoenix, "~> 1.1"},
- {:opentelemetry_cowboy, "~> 0.2"}
+ {:opentelemetry_cowboy, "~> 0.2"},
+ {:live_monaco_editor, "~> 0.1"}
]
end
diff --git a/mix.lock b/mix.lock
index fc379db6f..24bae1459 100644
--- a/mix.lock
+++ b/mix.lock
@@ -74,6 +74,7 @@
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"key_tools": {:hex, :key_tools, "0.4.1", "4bdf5a39190dc465e58f0c44784b7bb5300bafbbfff2b4ada4d7ec3bfde8d470", [:mix], [], "hexpm", "1a5afce636176481acec2db91066e68af5bf3c512327292a14078ca1aad1a57e"},
"libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"},
+ "live_monaco_editor": {:hex, :live_monaco_editor, "0.1.8", "149c02cab1c595fe2d2049cffb0a424db2a329a5fa848ee8b778d5acd8694733", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "9a56e88a61cdf6d58081627e4842f4e3a8e3a75dd8749f271a464164ad4530f1"},
"local_cluster": {:hex, :local_cluster, "1.2.1", "8eab3b8a387680f0872eacfb1a8bd5a91cb1d4d61256eec6a655b07ac7030c73", [:mix], [{:global_flags, "~> 1.0", [hex: :global_flags, repo: "hexpm", optional: false]}], "hexpm", "aae80c9bc92c911cb0be085fdeea2a9f5b88f81b6bec2ff1fec244bb0acc232c"},
"logflare_api_client": {:hex, :logflare_api_client, "0.3.5", "c427ebf65a8402d68b056d4a5ef3e1eb3b90c0ad1d0de97d1fe23807e0c1b113", [:mix], [{:bertex, "~> 1.3", [hex: :bertex, repo: "hexpm", optional: false]}, {:finch, "~> 0.10", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "16d29abcb80c4f72745cdf943379da02a201504813c3aa12b4d4acb0302b7723"},
"logflare_etso": {:hex, :logflare_etso, "1.1.2", "040bd3e482aaf0ed20080743b7562242ec5079fd88a6f9c8ce5d8298818292e9", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "ab96be42900730a49b132891f43a9be1d52e4ad3ee9ed9cb92565c5f87345117"},
diff --git a/test/logflare_web/live_views/query_live_test.exs b/test/logflare_web/live_views/query_live_test.exs
new file mode 100644
index 000000000..54a62dba6
--- /dev/null
+++ b/test/logflare_web/live_views/query_live_test.exs
@@ -0,0 +1,43 @@
+defmodule LogflareWeb.QueryLiveTest do
+ @moduledoc false
+ use LogflareWeb.ConnCase
+
+ setup %{conn: conn} do
+ insert(:plan)
+ user = insert(:user)
+ conn = login_user(conn, user)
+ {:ok, user: user, conn: conn}
+ end
+
+ describe "query page" do
+ test "run a valid query", %{conn: conn} do
+ GoogleApi.BigQuery.V2.Api.Jobs
+ |> expect(:bigquery_jobs_query, 1, fn _conn, _proj_id, _opts ->
+ {:ok, TestUtils.gen_bq_response([%{"ts" => "some-data"}])}
+ end)
+
+ {:ok, view, _html} = live(conn, "/query")
+
+ # link to show
+ view
+ |> element("form")
+ |> render_submit(%{
+ live_monaco_editor: %{
+ query: "select current_timestamp() as ts"
+ }
+ }) =~ "Ran query successfully"
+
+ assert_patch(view) =~ ~r/current_timestamp/
+ assert render(view) =~ "some-data"
+ end
+
+ test "parser error", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/query")
+
+ assert view
+ |> render_hook("parse-query", %{
+ value: "select current_datetime() order-by invalid"
+ }) =~ "parser error"
+ end
+ end
+end
From fcff7accd62f89fe70aa2324520fed1b0474e3f6 Mon Sep 17 00:00:00 2001
From: TzeYiing
Date: Tue, 3 Sep 2024 03:42:20 +0800
Subject: [PATCH 2/4] docs: add in-app docs on query composition
---
lib/logflare_web/live/query_live.ex | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/logflare_web/live/query_live.ex b/lib/logflare_web/live/query_live.ex
index a529c00f7..437b6deb1 100644
--- a/lib/logflare_web/live/query_live.ex
+++ b/lib/logflare_web/live/query_live.ex
@@ -25,6 +25,7 @@ defmodule LogflareWeb.QueryLive do
Always have a filter over the timestamp
column in your WHERE
clause
Use CROSS JOIN UNNEST(my_table.my_column) as col
to use nested fields in your query
Smaller time ranges load faster
+ Endpoint and alert queries can be referenced using `MyEndpointName`
for query composition
Read the docs
to find out more about querying Logflare with BigQuery SQL
From 1a70d9d15501b8200c808666240e23392092db64 Mon Sep 17 00:00:00 2001
From: TzeYiing
Date: Tue, 3 Sep 2024 14:00:35 +0800
Subject: [PATCH 3/4] chore: version bump
---
VERSION | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VERSION b/VERSION
index ff2fd4fbe..9eadd6baa 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.8.5
\ No newline at end of file
+1.8.6
\ No newline at end of file
From 4d4a16742f131254334ce35fb65ec142a67af699 Mon Sep 17 00:00:00 2001
From: TzeYiing
Date: Tue, 3 Sep 2024 14:02:50 +0800
Subject: [PATCH 4/4] chore: remove react-data-grid
---
assets/js/interfaces/DataTable.jsx | 13 ---------
assets/js/interfaces/index.js | 1 -
assets/package-lock.json | 45 ------------------------------
assets/package.json | 1 -
4 files changed, 60 deletions(-)
delete mode 100644 assets/js/interfaces/DataTable.jsx
diff --git a/assets/js/interfaces/DataTable.jsx b/assets/js/interfaces/DataTable.jsx
deleted file mode 100644
index 29a62f984..000000000
--- a/assets/js/interfaces/DataTable.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-
-import DataGrid from 'react-data-grid';
-
-function DataTable({columns, rows} = props) {
- console.log(props)
- return rows[i]}
- rowsCount={3}
- minHeight={150}
- />;
-}
-
-export default DataTable
\ No newline at end of file
diff --git a/assets/js/interfaces/index.js b/assets/js/interfaces/index.js
index 2c3d0d294..c13eaff46 100644
--- a/assets/js/interfaces/index.js
+++ b/assets/js/interfaces/index.js
@@ -1,4 +1,3 @@
export {default as EndpointEditor} from "./EndpointEditor.jsx"
export {default as EndpointsBrowserList} from "./EndpointsBrowserList.jsx"
export {default as ShowEndpoint} from "./ShowEndpoint.jsx"
-export {default as DataTable} from "./DataTable.jsx"
diff --git a/assets/package-lock.json b/assets/package-lock.json
index 26067f88f..48ce771d7 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -26,7 +26,6 @@
"phoenix_live_view": "file:../deps/phoenix_live_view",
"react": "^16.11.0",
"react-bootstrap": "^1.6.6",
- "react-data-grid": "^6.1.0",
"react-dom": "^16.11.0",
"react-spinners": "^0.9.0",
"sql-formatter": "^2.3.3"
@@ -2647,20 +2646,6 @@
"react-dom": ">=16.8.0"
}
},
- "node_modules/react-data-grid": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-6.1.0.tgz",
- "integrity": "sha512-N1UtiHvsowEPzhx0VPqQKvGgSza/YNljczbisFDGMjawiGApS2taMv7h+EDXDx49CdaA6ur4eYS0z10x63IUpw==",
- "dependencies": {
- "object-assign": "^4.1.1",
- "react-is-deprecated": "^0.1.2",
- "shallowequal": "^1.1.0"
- },
- "peerDependencies": {
- "react": "^16.0.0",
- "react-dom": "^16.0.0"
- }
- },
"node_modules/react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
@@ -2680,11 +2665,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
- "node_modules/react-is-deprecated": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/react-is-deprecated/-/react-is-deprecated-0.1.2.tgz",
- "integrity": "sha512-n3Y04lqbuwMiSywwAKBwW89YxAPuFwS5tYA4L6wDGLQCdSsod1KSfzCIiTTUvS9hPdaB39HdvxjxAaS0Lk4h+A=="
- },
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
@@ -2850,11 +2830,6 @@
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
- "node_modules/shallowequal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
- "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -5217,16 +5192,6 @@
"warning": "^4.0.3"
}
},
- "react-data-grid": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/react-data-grid/-/react-data-grid-6.1.0.tgz",
- "integrity": "sha512-N1UtiHvsowEPzhx0VPqQKvGgSza/YNljczbisFDGMjawiGApS2taMv7h+EDXDx49CdaA6ur4eYS0z10x63IUpw==",
- "requires": {
- "object-assign": "^4.1.1",
- "react-is-deprecated": "^0.1.2",
- "shallowequal": "^1.1.0"
- }
- },
"react-dom": {
"version": "16.14.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
@@ -5243,11 +5208,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
- "react-is-deprecated": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/react-is-deprecated/-/react-is-deprecated-0.1.2.tgz",
- "integrity": "sha512-n3Y04lqbuwMiSywwAKBwW89YxAPuFwS5tYA4L6wDGLQCdSsod1KSfzCIiTTUvS9hPdaB39HdvxjxAaS0Lk4h+A=="
- },
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
@@ -5365,11 +5325,6 @@
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
- "shallowequal": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
- "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
- },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index d79e26c3f..0995486c7 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -26,7 +26,6 @@
"phoenix_live_view": "file:../deps/phoenix_live_view",
"react": "^16.11.0",
"react-bootstrap": "^1.6.6",
- "react-data-grid": "^6.1.0",
"react-dom": "^16.11.0",
"react-spinners": "^0.9.0",
"sql-formatter": "^2.3.3"