197 lines
7.4 KiB
JavaScript
197 lines
7.4 KiB
JavaScript
const TEST_REQUEST_KEY = "test_view_query";
|
|
|
|
export function collectionViewQueryTab(upsertData) {
|
|
const uniqueId = "query_" + app.utils.randomString();
|
|
|
|
// dprint-ignore
|
|
const autocomplete = [
|
|
"SELECT", "FROM", "WHERE", "LEFT JOIN", "INNER JOIN", "ON",
|
|
"GROUP BY", "HAVING", "ORDER BY", "LIMIT", "OFFSET", "AS",
|
|
"WITH", "NOT", "IN", "EXISTS", "LIKE", "CAST",
|
|
];
|
|
|
|
const local = store({
|
|
testRecords: [],
|
|
testError: "",
|
|
isTesting: false,
|
|
});
|
|
|
|
async function dryRunViewQuery(query) {
|
|
local.isTesting = true;
|
|
|
|
local.testRecords = [];
|
|
|
|
// reset form errors related to the query
|
|
if (app.store.errors?.viewQuery || app.store.errors?.fields) {
|
|
delete app.store.errors.viewQuery;
|
|
delete app.store.errors.fields;
|
|
}
|
|
|
|
if (!query) {
|
|
local.testError = "";
|
|
local.isTesting = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// @todo replace with SDK method
|
|
const result = await app.pb.send("/api/collections/meta/dry-run-view", {
|
|
method: "POST",
|
|
body: { "query": query },
|
|
requestKey: TEST_REQUEST_KEY,
|
|
});
|
|
|
|
if (upsertData.collection?.id) {
|
|
// replace the collection meta fields
|
|
local.testRecords = result.sample.map((r) => {
|
|
r.collectionId = upsertData.collection?.id;
|
|
r.collectionName = upsertData.collection?.name;
|
|
return r;
|
|
});
|
|
} else {
|
|
local.testRecords = result.sample;
|
|
}
|
|
|
|
local.testError = "";
|
|
local.isTesting = false;
|
|
} catch (err) {
|
|
if (!err.isAbort) {
|
|
local.testError = err.message || "Invalid query.";
|
|
local.isTesting = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
let testDebounceId;
|
|
|
|
const watchers = [
|
|
watch(() => upsertData.collection?.viewQuery, (newQuery) => {
|
|
clearTimeout(testDebounceId);
|
|
testDebounceId = setTimeout(() => dryRunViewQuery(newQuery), 200);
|
|
}),
|
|
];
|
|
|
|
return t.div(
|
|
{
|
|
pbEvent: "collectionViewQueryTabContent",
|
|
className: "collection-tab-content collection-view-query-tab-content",
|
|
onunmount: () => {
|
|
clearTimeout(testDebounceId);
|
|
app.pb.cancelRequest(TEST_REQUEST_KEY);
|
|
watchers.forEach((w) => w?.unwatch());
|
|
},
|
|
},
|
|
t.div(
|
|
{ className: "grid" },
|
|
t.div(
|
|
{ className: "col-12" },
|
|
t.div(
|
|
{ className: "txt-right txt-sm m-b-10" },
|
|
t.button(
|
|
{
|
|
type: "button",
|
|
className: "txt-bold link-hint",
|
|
"html-popovertarget": uniqueId + "caveats_dropdown",
|
|
},
|
|
() => "Query caveats",
|
|
),
|
|
),
|
|
t.div(
|
|
{
|
|
id: uniqueId + "caveats_dropdown",
|
|
className: "dropdown sm query-caveats-dropdown",
|
|
popover: "auto",
|
|
},
|
|
t.ul(
|
|
null,
|
|
t.li(null, "Wildcard columns (*) are not supported."),
|
|
t.li(
|
|
null,
|
|
"The query must have a unique ",
|
|
t.code(null, "id"),
|
|
" column.",
|
|
t.br(),
|
|
"If your query doesn't have a suitable one, you can use the universal ",
|
|
t.code(null, "(ROW_NUMBER() OVER()) as id"),
|
|
".",
|
|
),
|
|
t.li(
|
|
null,
|
|
"Expressions must be aliased with a valid formatted field name, e.g. ",
|
|
t.code(null, "MAX(balance) as maxBalance"),
|
|
".",
|
|
),
|
|
t.li(
|
|
null,
|
|
"Combined/multi-spaced expressions must be wrapped in parenthesis, e.g. ",
|
|
t.code(null, "(MAX(balance) + 1) as maxBalance"),
|
|
".",
|
|
),
|
|
t.li(
|
|
null,
|
|
"UNION expressions are supported but the entire query must be wrapped in parenthesis.",
|
|
),
|
|
),
|
|
),
|
|
t.div(
|
|
{ className: "field" },
|
|
t.label(
|
|
{ htmlFor: uniqueId + ".viewQuery" },
|
|
t.span({ className: "txt" }, "Select query"),
|
|
t.span(
|
|
{
|
|
hidden: () => !local.testError,
|
|
className: "query-state",
|
|
ariaDescription: app.attrs.tooltip("Invalid query", "left"),
|
|
},
|
|
t.i({ className: "ri-error-warning-fill txt-danger", ariaHidden: true }),
|
|
),
|
|
t.span(
|
|
{
|
|
hidden: () => !!local.testError,
|
|
className: "query-state",
|
|
ariaDescription: app.attrs.tooltip("Valid query", "left"),
|
|
},
|
|
t.i({ className: "ri-checkbox-circle-fill txt-success", ariaHidden: true }),
|
|
),
|
|
),
|
|
app.components.codeEditor({
|
|
id: uniqueId + ".viewQuery",
|
|
name: "viewQuery",
|
|
language: "sql",
|
|
required: true,
|
|
autocomplete: autocomplete,
|
|
className: "inline-error",
|
|
value: () => upsertData.collection.viewQuery || "",
|
|
oninput: (newVal) => {
|
|
upsertData.collection.viewQuery = newVal;
|
|
},
|
|
}),
|
|
),
|
|
),
|
|
t.div(
|
|
{ className: "col-12" },
|
|
t.p(
|
|
{ className: "txt-sm txt-bold" },
|
|
"Sample output:",
|
|
),
|
|
t.div(
|
|
{ className: "view-query-sample-wrapper" },
|
|
t.span({ hidden: () => !local.isTesting, className: "loader sm" }),
|
|
app.components.codeBlock({
|
|
language: () => local.testError ? "plain" : "js",
|
|
className: () => `view-query-sample ${local.testError ? "txt-danger" : ""}`,
|
|
value: () => {
|
|
if (local.testRecords?.length) {
|
|
return JSON.stringify(local.testRecords, null, 2);
|
|
}
|
|
|
|
return local.testError || "N/A";
|
|
},
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|