=== Glimpse BI ===
Contributors: belloglimpsebi, abdussamadbello
Tags: ai, dashboard, charts, analytics, sql
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Charts and dashboards from your WordPress tables. Build with clicks or read-only SQL, or ask in plain English with optional Ask.

== Description ==

Glimpse BI turns your WordPress database into charts and dashboards inside wp-admin. Build them three ways: a no-code builder, safe read-only SQL, or Ask, which answers plain-English questions with a chart and shows the query it ran. Use your own posts, users, WooCommerce orders, KiviCare appointments, or any other table, without exporting data to a third party.

Every dashboard tile reads through a stale-while-revalidate cache, so multiple admins viewing the same dashboard share one query roundtrip per TTL window. SQL-mode charts pass through an AST-level validator (read-only `SELECT`/`WITH ... SELECT` only) before execution, so power users can write hand-tuned queries without giving up safety.

Features:

* Ask (optional) — ask questions about your data in plain language and get charts, numbers and tables back, with the query that produced them. Ask is a service: it needs a Glimpse account and runs on Glimpse credits (new accounts include trial credits). It is off until an administrator connects the site and accepts the privacy notice, and every query still runs on your own database. See "External services" below.
* No-code chart builder (GUI mode) — pick a table, measure, dimensions, and filters
* SQL mode for hand-written queries — validated server-side, read-only, parameterized
* React-based dashboard grid with 12-column responsive layout (react-grid-layout)
* Runtime parameters per chart (search boxes, date ranges, dropdowns) backed by `wpdb->prepare`
* Public embed iframes with token auth, rate limiting, and CSV / JSON export
* Built-in dashboard presets — install ready-made dashboards for plugins like KiviCare in one click
* ApexCharts visualizations with a shared design-token theme (KPI, line, bar, pie, table, heatmap, funnel)
* CSV / JSON exports per chart and per dashboard
* Schema introspection with a 1-hour cache and manual refresh

Designed to be polite to your database: query timeouts, row limits (configurable, default 10K), regen-stampede locks, and persistent-object-cache support (auto-detected — uses `wp_cache_*` when available, transients otherwise).

== External services ==

Glimpse BI works entirely inside your site. The one exception is **Ask**, an optional feature that uses the Glimpse AI service. Nothing is sent anywhere until an administrator has **connected the site to a Glimpse account** (Glimpse BI → Ask → Connect to Glimpse) **and accepted the Ask privacy notice**. Without both, Glimpse BI makes no outbound requests for Ask, and every other feature (charts, dashboards, SQL mode, embeds, exports) works the same whether or not a site ever connects.

**Glimpse relay (api.glimpsebi.com), operated by Glimpse BI.** The relay chooses the AI model, adds the instructions, runs the model and bills the account's Glimpse credits. The plugin contacts it:

* When an administrator connects the site: the browser is sent to glimpsebi.com/app/connect with the site address (home URL), a random site ID generated by the plugin, the address of this site's Ask page and a one-time security token. After the administrator approves, the plugin exchanges a one-time code for a connection key (`POST /connect/exchange`, sending the code and the site ID). When an administrator disconnects, the plugin asks the relay to revoke that key (`POST /connect/revoke`).
* When a user with access asks a question (`POST /ai/step`, once per step of an answer) and to show the credit balance (`GET /ai/balance`). Each request carries the connection key and the site ID.

What a question sends:

* The question and the earlier messages of the same conversation, including the AI's previous replies and short summaries of the steps it took.
* The names of the tables and columns Ask is allowed to see, their column types, column counts and approximate row counts. Tables marked private and hidden columns (by default anything that looks like an email, password, token, secret, API key, IP address or phone number, plus the users, usermeta and options tables) are never sent.
* Glossary terms an administrator added, and on dashboards and in the chart builder the titles and definitions of the charts in view.
* The site's language, time zone and currency, and the current time.
* Only at the data levels an administrator chooses in Ask settings: short lists of distinct column values (for example order statuses; "Schema + values"), or up to 200 result rows of the queries Ask ran ("Full results"). The default, "Schema only", sends no values or rows from your tables.

The queries themselves run on your own database; the relay never receives database credentials and never runs queries. The relay keeps no copy of questions, conversations or results; it records usage (time, model, token counts and credits) for billing.

**Cloudflare Workers AI**, operated by Cloudflare, Inc., runs the model for the relay, through Cloudflare AI Gateway, whenever a question is asked. The relay forwards the data listed above. The models are **DeepSeek** models (DeepSeek V4) hosted on Cloudflare's infrastructure; nothing is sent to DeepSeek's own service. Your site never contacts Cloudflare or DeepSeek itself.

* Glimpse Terms of Use: https://glimpsebi.com/terms
* Glimpse Privacy Policy: https://glimpsebi.com/privacy
* Glimpse Data Processing Addendum: https://glimpsebi.com/dpa
* Cloudflare Website and Online Services Terms of Use: https://www.cloudflare.com/website-terms/
* Cloudflare Privacy Policy: https://www.cloudflare.com/privacypolicy/

== Installation ==

1. Upload the plugin zip via Plugins → Add New → Upload Plugin, or unzip into `wp-content/plugins/`.
2. Activate "Glimpse BI" from the Plugins screen.
3. Visit Glimpse BI → Charts to build your first chart, or install a ready-made dashboard from the Presets section at the top of the Charts page.

The plugin self-migrates schema on every activation and on `plugins_loaded` (compares stored `glimpsebi_db_version` against the bundled constant).

== Frequently Asked Questions ==

= Does this run my SQL directly? =

Only `SELECT` statements (and `WITH ... SELECT` CTEs) pass the server-side AST validator. Multi-statement queries, `INTO OUTFILE`, `LOAD_FILE`, `SLEEP`, and (by default) `information_schema` access are rejected.

= Will it slow down my site? =

Chart results pass through a stale-while-revalidate cache (default TTL: 15 min) keyed by chart + definition + runtime params. A regen lock prevents thundering-herd queries when the cache expires. Schema introspection is cached for 1 hour and refreshed manually from the Settings page.

= Where is the source code? =

The full TypeScript/React source ships in the plugin's `src/` folder; each file in `assets/build/` is built from an entry in `src/entries/`. To rebuild, run `npm ci` and then `npm run build` in the plugin folder (Node.js 20.19+ or 22.12+). `package.json`, `package-lock.json` and the Vite/PostCSS configs list every dependency.

Bundled open-source libraries: [React](https://github.com/facebook/react), [ApexCharts](https://github.com/apexcharts/apexcharts.js), [react-apexcharts](https://github.com/apexcharts/react-apexcharts), [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout), [CodeMirror](https://github.com/codemirror/dev), [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror), [Radix UI](https://github.com/radix-ui/primitives), [cmdk](https://github.com/pacocoursey/cmdk), [Lucide](https://github.com/lucide-icons/lucide), [next-themes](https://github.com/pacocoursey/next-themes), [Sonner](https://github.com/emilkowalski/sonner), [clsx](https://github.com/lukeed/clsx), [tailwind-merge](https://github.com/dcastil/tailwind-merge) and [class-variance-authority](https://github.com/joe-bell/cva), built with [Vite](https://github.com/vitejs/vite) and [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss). PHP libraries in `vendor/`: [PHP-SQL-Parser](https://github.com/greenlion/PHP-SQL-Parser) and [Vite for WP](https://github.com/kucrut/vite-for-wp).

= Does Glimpse BI send my data anywhere? =

No, unless you turn on Ask. Charts, dashboards, SQL mode, embeds and exports run entirely on your site. Ask is optional: it sends data to the Glimpse AI service only after an administrator connects the site to a Glimpse account and accepts the privacy notice, and then only what is listed under "External services" — by default the question, the conversation and the names of the tables and columns Ask may see, never rows from your tables. You can disconnect at any time in Glimpse BI → Ask → Ask settings.

= What does Ask cost? =

Ask runs on Glimpse credits, bought in your Glimpse account at https://glimpsebi.com/app/billing. New accounts get trial credits on their first connection. Glimpse BI Pro plans include a monthly allowance. Everything else in the plugin is free.

= Is there a Pro version? =

Yes. Glimpse BI Pro is a separately distributed add-on with its own implementation code. This WordPress.org plugin is fully functional without a license key.

== Screenshots ==

1. Ask: a plain-English question answered with a chart, the steps it took, and how the result was computed.
2. A dashboard: KPIs, a revenue trend and an order-status breakdown, with a date-range filter.
3. The chart builder: pick a table, a measure and a grouping. The preview and the read-only SQL update as you go.
4. SQL mode: write your own query for joins and custom logic. It is checked to be read-only before it runs.
5. The chart library: every saved chart with a live preview, ready to share, embed or export.
6. A chart embedded in a post with a shortcode.
7. Data access: choose which database tables can be charted.

== Upgrade Notice ==

= 1.1.0 =
New: Ask, an optional AI assistant that answers questions about your data with charts. Off until an administrator connects the site and agrees to the privacy notice. Also fixes embedded dashboards ignoring the date range.

= 1.0.3 =
Builder charts can no longer read tables that Settings hides. Database errors now show on the chart instead of an empty result. Adds Min, Max and Count distinct measures.

= 1.0.2 =
Security fix: chart labels are now escaped in tooltips and legends. Dashboard exports respect private-chart access. Recommended for all users.

= 1.0.1 =
Fixes empty chart and dashboard exports and a fatal error when exporting a whole dashboard. Adds longer date ranges and a custom From/To picker.

= 1.0.0 =
First stable release on WordPress.org.

== Changelog ==

= 1.1.0 =
* New: Ask — ask questions about your data in plain language and get a chart, number or table back, with the query it ran. Ask is optional and off by default: an administrator connects the site to a Glimpse account and accepts the privacy notice first. It runs on Glimpse credits; queries still run on your own database, and private tables and hidden columns never leave the site. See "External services".
* New: Ask settings control what Ask may see (schema only, schema + values, or full results), private tables, hidden columns, a glossary, a daily limit per person, thread retention and an optional audit log of what was sent.
* Developer: `glimpsebi_ai_relay_credential`, `glimpsebi_ai_monthly_limit` and `glimpsebi_ai_role_grants_available` filters for add-ons. `GLIMPSEBI_API_BASE` and `GLIMPSEBI_PORTAL_URL` can be overridden in `wp-config.php`.
* Changed: uninstalling also removes Ask's tables, settings, connection and scheduled clean-up.
* Fixed: embedded dashboards now apply the selected date range to every chart, including charts scoped through the dashboard's filter mappings, and so do their per-chart exports.
* Fixed: charts saved without an explicit builder mode opened as an empty SQL editor. They now open in the chart builder.
* Fixed: the preset card's Install button was hard to read.
* Fixed: table charts showed internal column names (DIM, MEASURE) as headers. They now show the field and measure, for example "status" and "Count".

= 1.0.3 =
* Security: the schema allowlist is now enforced on the server when builder (GUI) charts are previewed and saved, not just hidden in the builder. A builder chart can no longer read a table that Settings hides, such as the users table. Existing saved charts keep rendering. SQL-mode charts, which only administrators can write, are not restricted by the allowlist.
* Fixed: a chart whose query the database rejects (for example a column that no longer exists) now shows an error instead of an empty chart, and the error is never cached.
* Fixed: previewing or saving a chart that references a missing table or column now names the exact field to fix.
* Developer: chart definitions accept Min, Max and Count distinct measures and an optional `order` (by value or group) for top-N charts; `GlimpseBI_Query::json_schema()` and `GlimpseBI_Query::describe()` expose the definition contract.
* Changed: the readme documents where the source of every compiled file lives and how to rebuild it, with links to each bundled library's source.

= 1.0.2 =
* Security: chart labels and series names are now escaped in chart tooltips and legends, so text stored in your data (post titles, customer names) can never run as code in wp-admin or on embeds.
* Security: dashboard exports only include charts the current user is allowed to see.
* Fixed: uninstalling now also removes dashboard presets and the stored schema version.
* Changed: the bundled JavaScript can be rebuilt from the included source with `npm ci && npm run build` from the plugin folder.

= 1.0.1 =
* Fixed: chart and dashboard exports could download as empty files. Exports now use the dashboard's selected date range, ignore filters a chart doesn't use, and show a clear message when a chart still needs filter values instead of silently returning nothing.
* Fixed: exporting a whole dashboard failed with a fatal error.
* Fixed: "New dashboard", "Duplicate" and embed-token creation could throw a script error.
* New: date ranges Last quarter, Last 6 months, Last 12 months and Last year, plus a custom From/To picker on dashboards. Embedded dashboards offer the longer ranges too.
* Changed: re-installing a preset over an existing one now keeps your existing dashboard and charts (renamed "(previous)") instead of deleting them.
* Developer: new `glimpsebi_admin_export_formats` filter lets add-ons offer extra export formats in the Export menus.

= 1.0.0 =
* First public release on WordPress.org. Self-serve BI inside wp-admin: a no-code chart builder and validated read-only SQL mode over your existing WordPress tables (WooCommerce, members, bookings, and more), a responsive dashboard grid, public embed iframes with token auth, CSV/JSON exports, dashboard presets, and a stale-while-revalidate cache that keeps it polite to your database. Consolidates all prior 0.x development into a stable 1.0.

= 0.4.4 =
* Changed: the chart builder's Chart-tab preview now shows table charts as a capped 25-row sample (with a "first 25 of N rows" note) instead of the full paginated tile. The builder is for shaping a definition, not browsing the whole result set — the live dashboard tile and public embed still show all rows with pagination/search. No effect on non-table chart types.

= 0.4.3 =
* New: dashboard toolbar now has a kebab menu next to the dashboard selector with Rename, Duplicate, and Delete actions.
* New: `POST /dashboards/{id}/duplicate` endpoint. Copies the layout and pinned charts (charts are referenced, not cloned — editing a chart still reflects on every dashboard that pins it). New dashboard is named "{Original} (copy)" with an incrementing suffix if needed.
* Note: the Home dashboard cannot be deleted (the menu item is disabled with a tooltip explaining why; the server also enforces this).

= 0.4.2 =
* Fixed: public embed iframes refused to render on hosts whose nginx/security layer also sets `X-Frame-Options: SAMEORIGIN`. We were sending the (invalid, non-spec) `X-Frame-Options: ALLOWALL`; browsers see the two conflicting values and fall back to deny. Now we remove any prior X-Frame-Options and send the spec-compliant `Content-Security-Policy: frame-ancestors *` instead, which takes precedence over X-Frame-Options in modern browsers per CSP Level 2.

= 0.4.1 =
* Fixed: sticky column headers in table charts now have an opaque background, so scrolling rows no longer bleed through them.
* Fixed: rows-per-page selector now has a visible border, hover state, focus ring, and pointer cursor so it actually looks like a control instead of label text.

= 0.4.0 =
* New: every table chart now has proper Prev/Next pagination + a rows-per-page selector (25/50/100/200). Previously, only the first 50 filtered rows were visible regardless of how many the SQL returned. Search now resets to page 1 automatically.
* New (kivicare preset): Encounters KPI + table, sourced from `wp_kc_patient_encounters` (the clinical-visits table, distinct from appointments). The dashboard's Services charts were inadvertently counting service-line-items-on-appointments, not encounters — so visit volume was missing entirely.
* Changed (kivicare preset): the Services charts now show the catalog. Total Services = distinct active services in `wp_kc_services`. Services table = catalog rows plus `Times Performed` and `Distinct Doctors` columns aggregated from `wp_kc_appointment_service_mapping` in the selected date range. One table answers both "what do we offer?" and "what's getting used?"
* Changed: directory-table SQL LIMITs bumped from 200 to 2000 so pagination has room to navigate large clinics.
* Existing kivicare installs: Library -> Presets -> KiviCare -> Install (Replace) to pick up the new charts + layout.

= 0.3.4 =
* Fixed: KiviCare preset queries now seed from the `wp_kc_{doctor,patient,receptionist}_clinic_mappings` tables instead of scanning `wp_usermeta` for `LIKE '%kiviCare_*%'`. The capability LIKE pattern was forcing a full table scan on every chart load — measured 5–6s cold-start with 196 doctors. The rewrite drops that to <1s by seeding from a small indexed table. Existing installs: Library → Presets → KiviCare → Install (Replace) to pick up the new SQL.
* Note: an unintended side-effect is that doctors/patients/receptionists who have the WordPress role but aren't assigned to any clinic disappear from the directory tables. This matches typical "operations dashboard" intent (unassigned = inactive). Open an issue if you need the old behavior back.

= 0.3.3 =
* Fixed: dashboards with many tiles no longer storm shared/managed WP hosts with parallel chart-data requests. The client now loads charts in batches of 3 at a time, and transient upstream failures (502/503/504) are retried once with a short backoff. Eliminates the "first few tiles load, the rest 502" failure mode on EasyWP and other tight PHP-FPM pools.

= 0.3.2 =
* Fixed: write requests now succeed on shared/managed WP hosts whose WAFs reject both PATCH/DELETE verbs *and* the `X-HTTP-Method-Override` header (Imunify360 on EasyWP, observed in the wild). Every PATCH route now also accepts plain POST on the same URL; every DELETE route has a parallel `POST {path}/delete` alias. The client sends plain POST with no special headers — invisible to WAFs that pattern-match suspicious patterns.

= 0.3.1 =
* Attempted fix using `X-HTTP-Method-Override`. Worked locally but the header itself is WAF-flagged on some hosts. Superseded by 0.3.2 — please upgrade directly to 0.3.2.

= 0.3.0 =
* New: dashboard presets registry — install ready-made dashboards in one click, including a KiviCare clinic-operations preset.
* New: per-dashboard date-range global filter that auto-injects into SQL charts declaring `date_from` / `date_to` parameters.
* New: embed iframes now expose a date-range picker and per-chart CSV / JSON download links.
* Fixed: chart builder no longer crashes when loading preset-installed SQL charts (made `column_mapping` optional).
* Fixed: REST preview returns 400 (was 500) for missing required runtime parameters.

= 0.2.0 =
* Earlier release; see commit history for details.
