Skip to content
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

feat(admin): Add a standardized component for copying query results #6789

Merged
merged 1 commit into from
Jan 20, 2025
Merged
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
13 changes: 7 additions & 6 deletions snuba/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"@mantine/core": "6.0.21",
"@mantine/dates": "6.0.21",
"@mantine/hooks": "6.0.21",
"@mantine/prism": "^6.0.15",
"@mantine/tiptap": "^6.0.15",
"@mantine/prism": "^6.0.21",
"@mantine/tiptap": "^6.0.21",
"@sentry/react": "^7.88.0",
"@tiptap/extension-code-block-lowlight": "^2.0.3",
"@tiptap/extension-link": "^2.0.3",
Expand All @@ -40,10 +40,11 @@
"devDependencies": {
"@emotion/react": "^11.13.3",
"@jest/globals": "^29.4.3",
"@mantine/core": "^6.0.15",
"@mantine/hooks": "^6.0.15",
"@mantine/prism": "^6.0.15",
"@mantine/tiptap": "^6.0.15",
"@mantine/core": "6.0.21",
"@mantine/dates": "6.0.21",
"@mantine/hooks": "6.0.21",
"@mantine/prism": "^6.0.21",
"@mantine/tiptap": "^6.0.21",
"@sentry/esbuild-plugin": "^2.22.7",
"@tabler/icons-react": "^3.17.0",
"@testing-library/react": "^14.0.0",
Expand Down
55 changes: 9 additions & 46 deletions snuba/admin/static/cardinality_analyzer/query_display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CardinalityQueryResult,
PredefinedQuery,
} from "SnubaAdmin/cardinality_analyzer/types";
import QueryResultCopier from "SnubaAdmin/utils/query_result_copier";

enum ClipboardFormats {
CSV = "csv",
Expand Down Expand Up @@ -42,24 +43,6 @@ function QueryDisplay(props: {
return CSV.sheet([queryResult.column_names, ...queryResult.rows]);
}

function copyText(
queryResult: CardinalityQueryResult,
format: ClipboardFormats
) {
let formatter: (input: CardinalityQueryResult) => string = (s) =>
s.toString();

if (format === ClipboardFormats.JSON) {
formatter = JSON.stringify;
}

if (format === ClipboardFormats.CSV) {
formatter = convertResultsToCSV;
}

window.navigator.clipboard.writeText(formatter(queryResult));
}

function executeQuery() {
return props.api
.executeCardinalityQuery(query as CardinalityQueryRequest)
Expand Down Expand Up @@ -92,41 +75,21 @@ function QueryDisplay(props: {
return (
<div key={idx}>
<p>{queryResult.input_query}</p>
<p>
<button
style={copyButtonStyle}
onClick={() => copyText(queryResult, ClipboardFormats.JSON)}
>
Copy to clipboard (JSON)
</button>
</p>
<p>
<button
style={copyButtonStyle}
onClick={() => copyText(queryResult, ClipboardFormats.CSV)}
>
Copy to clipboard (CSV)
</button>
</p>
<QueryResultCopier
jsonInput={JSON.stringify(queryResult)}
csvInput={convertResultsToCSV(queryResult)}
/>
{props.resultDataPopulator(queryResult)}
</div>
);
}

return (
<Collapse key={idx} text={queryResult.input_query}>
<button
style={copyButtonStyle}
onClick={() => copyText(queryResult, ClipboardFormats.JSON)}
>
Copy to clipboard (JSON)
</button>
<button
style={copyButtonStyle}
onClick={() => copyText(queryResult, ClipboardFormats.CSV)}
>
Copy to clipboard (CSV)
</button>
<QueryResultCopier
jsonInput={JSON.stringify(queryResult)}
csvInput={convertResultsToCSV(queryResult)}
/>
{props.resultDataPopulator(queryResult)}
</Collapse>
);
Expand Down
40 changes: 22 additions & 18 deletions snuba/admin/static/clickhouse_queries/query_display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Client from "SnubaAdmin/api_client";
import { Collapse } from "SnubaAdmin/collapse";
import QueryEditor from "SnubaAdmin/query_editor";
import ExecuteButton from "SnubaAdmin/utils/execute_button";
import QueryResultCopier from "SnubaAdmin/utils/query_result_copier";

import { SelectItem, Switch, Alert } from "@mantine/core";
import { Prism } from "@mantine/prism";
Expand Down Expand Up @@ -99,10 +100,6 @@ function QueryDisplay(props: {
});
}

function copyText(text: string) {
window.navigator.clipboard.writeText(text);
}

function getHosts(nodeData: ClickhouseNodeData[]): SelectItem[] {
let node_info = nodeData.find((el) => el.storage_name === query.storage)!;
// populate the hosts entries marking distributed hosts that are not also local
Expand Down Expand Up @@ -142,6 +139,17 @@ function QueryDisplay(props: {
setQueryError(error);
}

function convertResultsToCSV(queryResult: QueryResult) {
let output = queryResult.column_names.join(",");
for (const row of queryResult.rows) {
const escaped = row.map((v) =>
typeof v == "string" && v.includes(",") ? '"' + v + '"' : v
);
output = output + "\n" + escaped.join(",");
}
return output;
}

return (
<div>
<form style={query.sudo ? sudoForm : standardForm}>
Expand Down Expand Up @@ -202,27 +210,23 @@ function QueryDisplay(props: {
return (
<div key={idx}>
<p>{queryResult.input_query}</p>
<p>
<button
style={executeButtonStyle}
onClick={() => copyText(JSON.stringify(queryResult))}
>
Copy to clipboard
</button>
</p>
<QueryResultCopier
rawInput={queryResult.trace_output || ""}
jsonInput={JSON.stringify(queryResult)}
csvInput={convertResultsToCSV(queryResult)}
/>
{props.resultDataPopulator(queryResult)}
</div>
);
}

return (
<Collapse key={idx} text={queryResult.input_query}>
<button
style={executeButtonStyle}
onClick={() => copyText(JSON.stringify(queryResult))}
>
Copy to clipboard
</button>
<QueryResultCopier
rawInput={queryResult.trace_output || ""}
jsonInput={JSON.stringify(queryResult)}
csvInput={convertResultsToCSV(queryResult)}
/>
{props.resultDataPopulator(queryResult)}
</Collapse>
);
Expand Down
71 changes: 18 additions & 53 deletions snuba/admin/static/mql_queries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { CSV } from "../cardinality_analyzer/CSV";
import QueryResultCopier from "SnubaAdmin/utils/query_result_copier";

const MQLQueryExample = `(sum(d:transactions/duration@millisecond{status_code: 200}) by transaction + sum(d:transactions/duration@millisecond) by transaction) * 100.0`;

Expand Down Expand Up @@ -158,31 +159,13 @@ function MQLQueries(props: { api: Client }) {
queryResult: queryResultHistory[0],
})}
</div>
<Button.Group>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
JSON.stringify(queryResultHistory[0])
)
}
>
Copy to clipboard (JSON)
</Button>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
CSV.sheet([
queryResultHistory[0].columns,
...queryResultHistory[0].rows,
])
)
}
>
Copy to clipboard (CSV)
</Button>
</Button.Group>
<QueryResultCopier
jsonInput={JSON.stringify(queryResultHistory[0])}
csvInput={CSV.sheet([
queryResultHistory[0].columns,
...queryResultHistory[0].rows,
])}
/>
<Space h="md" />
<Table
headerData={queryResultHistory[0].columns}
Expand Down Expand Up @@ -216,7 +199,7 @@ function MQLQueries(props: { api: Client }) {
</>
)}
</div>
</div>
</div >
);
}

Expand Down Expand Up @@ -311,11 +294,11 @@ function QueryResultQuotaAllowance(props: { queryResult: QueryResult }) {
if (policy.max_threads < 10 && policy.explanation.reason != null) {
reasonHeader.push(
policyName +
": " +
policy.explanation.reason +
". MQL Query executed with " +
policy.max_threads +
" threads."
": " +
policy.explanation.reason +
". MQL Query executed with " +
policy.max_threads +
" threads."
);
}
});
Expand All @@ -339,28 +322,10 @@ function QueryResultHistoryItem(props: { queryResult: QueryResult }) {
Execution Duration (ms): {props.queryResult.duration_ms}
{QueryResultQuotaAllowance({ queryResult: props.queryResult })}
</div>
<Button.Group>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
JSON.stringify(props.queryResult)
)
}
>
Copy to clipboard (JSON)
</Button>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
CSV.sheet([props.queryResult.columns, ...props.queryResult.rows])
)
}
>
Copy to clipboard (CSV)
</Button>
</Button.Group>
<QueryResultCopier
jsonInput={JSON.stringify(props.queryResult)}
csvInput={CSV.sheet([props.queryResult.columns, ...props.queryResult.rows])}
/>
<Space h="md" />
<Table
headerData={props.queryResult.columns}
Expand Down
72 changes: 20 additions & 52 deletions snuba/admin/static/production_queries/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { CustomSelect, getParamFromStorage } from "SnubaAdmin/select";
import { useDisclosure } from "@mantine/hooks";
import { CSV } from "SnubaAdmin/cardinality_analyzer/CSV";
import { getRecentHistory, setRecentHistory } from "SnubaAdmin/query_history";
import QueryResultCopier from "SnubaAdmin/utils/query_result_copier";

const HISTORY_KEY = "production_queries";
function ProductionQueries(props: { api: Client }) {
Expand Down Expand Up @@ -133,31 +134,13 @@ function ProductionQueries(props: { api: Client }) {
queryResult: queryResultHistory[0],
})}
</div>
<Button.Group>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
JSON.stringify(queryResultHistory[0])
)
}
>
Copy to clipboard (JSON)
</Button>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
CSV.sheet([
queryResultHistory[0].columns,
...queryResultHistory[0].rows,
])
)
}
>
Copy to clipboard (CSV)
</Button>
</Button.Group>
<QueryResultCopier
jsonInput={JSON.stringify(queryResultHistory[0])}
csvInput={CSV.sheet([
queryResultHistory[0].columns,
...queryResultHistory[0].rows,
])}
/>
<Space h="md" />
<Table
headerData={queryResultHistory[0].columns}
Expand Down Expand Up @@ -266,11 +249,11 @@ function QueryResultQuotaAllowance(props: { queryResult: QueryResult }) {
if (policy.max_threads < 10 && policy.explanation.reason != null) {
reasonHeader.push(
policyName +
": " +
policy.explanation.reason +
". SnQL Query executed with " +
policy.max_threads +
" threads."
": " +
policy.explanation.reason +
". SnQL Query executed with " +
policy.max_threads +
" threads."
);
}
});
Expand All @@ -294,28 +277,13 @@ function QueryResultHistoryItem(props: { queryResult: QueryResult }) {
Execution Duration (ms): {props.queryResult.duration_ms}
{QueryResultQuotaAllowance({ queryResult: props.queryResult })}
</div>
<Button.Group>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
JSON.stringify(props.queryResult)
)
}
>
Copy to clipboard (JSON)
</Button>
<Button
variant="outline"
onClick={() =>
window.navigator.clipboard.writeText(
CSV.sheet([props.queryResult.columns, ...props.queryResult.rows])
)
}
>
Copy to clipboard (CSV)
</Button>
</Button.Group>
<QueryResultCopier
jsonInput={JSON.stringify(props.queryResult)}
csvInput={CSV.sheet([
props.queryResult.columns,
...props.queryResult.rows,
])}
/>
<Space h="md" />
<Table
headerData={props.queryResult.columns}
Expand Down
Loading
Loading