Skip to content

Commit

Permalink
display visual explains 🔮
Browse files Browse the repository at this point in the history
  • Loading branch information
tpetry committed Aug 5, 2024
1 parent ca895d3 commit b345ca4
Show file tree
Hide file tree
Showing 11 changed files with 2,275 additions and 1,748 deletions.
Binary file modified .github/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .github/demo.mp4
Binary file not shown.
12 changes: 6 additions & 6 deletions MysqlExplain.tableplusplugin/manifest.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
{
"name": "MySQL Explain",
"identifier": "com.explainmysql",
"version": "1.0",
"detail": "This plugin helps with analyzing MySQL and MariaDB queries.",
"name": "MySQL Visual Explain",
"identifier": "com.mysqlexplain",
"version": "2.0",
"detail": "This plugin helps with analyzing MySQL queries.",
"author": "Tobias Petry",
"authorEmail": "hello@tpetry.me",
"site": "https://explainmysql.com",
"site": "https://mysqlexplain.com",
"scripts": [
{
"location": "main",
"type": "action",
"shortcut": "control+e",
"script": "build/plugin.js",
"name": "Explain SQL"
"name": "Visual Explain SQL"
}
]
}
3,734 changes: 2,168 additions & 1,566 deletions MysqlExplain.tableplusplugin/package-lock.json

Large diffs are not rendered by default.

26 changes: 11 additions & 15 deletions MysqlExplain.tableplusplugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@
"author": "https://explainmysql.com",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.22.8",
"@babel/preset-env": "^7.22.7",
"babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"postcss": "^8.4.25",
"postcss-loader": "^7.3.3",
"postcss-preset-env": "^9.0.0",
"style-loader": "^3.3.3",
"webpack": "^5.88.1",
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"postcss": "^8.4.40",
"postcss-loader": "^8.1.1",
"postcss-preset-env": "^9.6.0",
"style-loader": "^4.0.0",
"webpack": "^5.93.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@ryangjchandler/alpine-clipboard": "^2.2.0",
"@ryangjchandler/alpine-tooltip": "^1.2.0",
"alpinejs": "^3.12.3",
"tailwindcss": "^3.3.2",
"tippy.js": "^6.3.7"
"tailwindcss": "^3.3.2"
}
}
20 changes: 7 additions & 13 deletions MysqlExplain.tableplusplugin/plugin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import { executeQuery } from './helpers';

function handleResults(context, sql, resExplainJson, resExplainTraditional, resExplainTree, resVersion, resWarnings) {
for (const res of [resExplainJson, resExplainTraditional, resVersion, resWarnings]) {
function handleResults(context, sql, resExplainJson, resExplainTree, resVersion) {
for (const res of [resExplainJson, resVersion]) {
if (res instanceof Error) {
context.alert('Error', res.message);

Expand All @@ -14,20 +14,18 @@ function handleResults(context, sql, resExplainJson, resExplainTraditional, resE
const data = {
query: sql,
version: resVersion[0]?.version,
explain_traditional: resExplainTraditional,
explain_json: resExplainJson[0]?.EXPLAIN,
explain_tree: resExplainTree[0]?.EXPLAIN ?? null,
warnings: resWarnings,
};

context
.loadFile(`${Application.pluginRootPath()}/com.explainmysql.tableplusplugin/build/ui.html`, null)
.evaluate(`submitPlan(${JSON.stringify(data, (_, value) => typeof value === 'undefined' ? null : value)})`);
.loadFile(`${Application.pluginRootPath()}/com.mysqlexplain.tableplusplugin/build/ui.html`, null)
.evaluate(`displayPlan(${JSON.stringify(data, (_, value) => typeof value === 'undefined' ? null : value)})`);
}

global.onRun = function(context) {
if (!['MariaDB', 'MySQL'].includes(context.driver())) {
context.alert('Error', 'Only MySQL and MariaDB databases are supported.');
if (!['MySQL'].includes(context.driver())) {
context.alert('Error', 'Only MySQL databases are supported.');

return;
}
Expand All @@ -46,17 +44,13 @@ global.onRun = function(context) {
// current one is finished (indicating sync logic). Therefore, the code needs to be developed in async way but not
// be complicated by pipelining the SQL queries.
let resExplainJson = null;
let resExplainTraditional = null;
let resExplainTree = null;
let resVersion = null;
let resWarnings = null;
executeQuery(context, 'SELECT VERSION() as version ', (res) => resVersion = res);
executeQuery(context, context.driver() === 'MariaDB' ? `EXPLAIN EXTENDED ${sql}` : `EXPLAIN FORMAT=TRADITIONAL ${sql}`, (res) => resExplainTraditional = res);
executeQuery(context, 'SHOW WARNINGS', (res) => resWarnings = res);
executeQuery(context, `EXPLAIN FORMAT=JSON ${sql}`, (res) => resExplainJson = res);
executeQuery(context, `EXPLAIN FORMAT=TREE ${sql}`, (res) => {
resExplainTree = res;

handleResults(context, sql, resExplainJson, resExplainTraditional, resExplainTree, resVersion, resWarnings);
handleResults(context, sql, resExplainJson, resExplainTree, resVersion);
});
};
4 changes: 0 additions & 4 deletions MysqlExplain.tableplusplugin/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./ui/*.html'],
theme: {
extend: {},
},
plugins: [],
}

7 changes: 5 additions & 2 deletions MysqlExplain.tableplusplugin/ui/index.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
@import "tippy.js/dist/tippy.css";

@tailwind base;
@tailwind components;
@tailwind utilities;

iframe {
width: 100%;
height: 100%;
}
120 changes: 26 additions & 94 deletions MysqlExplain.tableplusplugin/ui/index.html
Original file line number Diff line number Diff line change
@@ -1,103 +1,35 @@
<!DOCTYPE html>
<html class="h-full">
<html class="h-full w-full">
<head>
<meta charset="UTF-8">
<script src="./ui.js" defer></script>
<style>
[x-cloak] {
display: none !important;
}
</style>
<script src="./ui.js"></script>
</head>
<body class="h-full" x-data x-cloak x-show="true">
<div class="h-full flex justify-center items-center">
<div class="relative isolate">
<div class="absolute inset-x-0 -z-10 transform-gpu overflow-hidden blur-3xl -top-80" aria-hidden="true">
<div class="relative aspect-[1155/678] -translate-x-1/2 rotate-[30deg] bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 left-[calc(50%-30rem)] w-[72.1875rem]" style="clip-path: polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)"></div>
</div>
<div>
<div class="mx-auto max-w-7xl px-8">
<div class="mx-auto max-w-[45rem] text-center">
<span class="text-lg font-semibold leading-7 text-indigo-600">More Understandable. Finally :)</span>
<h1 class="mt-2 font-bold tracking-tight text-gray-900 text-6xl">
MySQL EXPLAIN
<span class="relative whitespace-nowrap text-indigo-600">
<svg aria-hidden="true" viewBox="0 0 418 42" class="absolute left-0 top-2/3 h-[0.58em] w-full fill-indigo-300/70" preserveAspectRatio="none">
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z"></path>
</svg>
<span class="relative">Explained</span>
</span>
</h1>
<div class="mt-6 text-lg leading-8 text-gray-600">
You no longer have to search for cryptic or incomprehensible information for EXPLAIN.<br>
All information has been transformed into data that is easier to grasp.
<ul role="list" class="grid grid-cols-1 gap-6 pt-6 mx-6">
<li class="col-span-1 divide-y divide-gray-200 rounded-lg bg-white shadow">
<div class="flex w-full items-center justify-between space-x-6 p-6">

<template x-data x-if="$store.state === 'loading'" hidden>
<div class="flex rounded-md shadow-sm w-full pointer-events-none">
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="animate-spin h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
</div>
<div class="block w-full rounded-none rounded-l-md border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm leading-6 text-left">
<span class="animate-pulse">Loading...</span>
</div>
</div>
<button type="button" class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-400 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
</svg>
Copy
</button>
</div>
</template>

<template x-data x-if="$store.state === 'error'" hidden>
<div class="rounded-md bg-red-50 p-4 text-left w-full">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800" x-text="$store.errorTitle"></h3>
<div class="mt-2 text-sm text-red-700">
<pre class="word-break whitespace-pre-wrap" x-text="$store.errorMessage"></pre>
</div>
</div>
</div>
</div>
</template>
<body class="h-full w-full">
<div id="explain" class="h-full w-full" style="display:none"></div>

<template x-data x-if="$store.state === 'success'" hidden>
<div class="flex rounded-md shadow-sm w-full">
<div class="relative flex flex-grow items-stretch focus-within:z-10">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244" />
</svg>
</div>
<input type="url" id="url" x-model="$store.url" class="block w-full rounded-none rounded-l-md border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 text-sm leading-6" value="">
</div>
<button type="button" @click="$clipboard($store.url); $tooltip('Copied to Clipboard!', { placement: 'bottom' })" class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
</svg>
Copy
</button>
</div>
</template>
<div id="loading" class="h-full w-full" style="display:none">
<div class="relative h-full w-full flex flex-col items-center justify-center bg-gray-200 bg-opacity-25">
<svg width="100" height="100" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>
<div class="pt-5 text-3xl font-medium">Submitting EXPLAIN plan</div>
</div>
</div>

</div>
</li>
</ul>
<span class="text-xs">Clickable URLs are impossible because of limitations with the TablePlus plugin framework.</span>
<div class="relative h-full w-full" id="error" style="display:none">
<div class="fixed inset-0 bg-gray-200 bg-opacity-25 transition-opacity"></div>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div class="sm:flex sm:items-start">
<div class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">Error</h3>
<div class="mt-2">
<p class="text-sm text-gray-500" id="error-message"></p>
</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit b345ca4

Please sign in to comment.