Files
appsmith-statistics-app/.cursor/skills/add-query-to-table/SKILL.md

102 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: add-query-to-table
description: Adds a new DB query to a page and wires it to a Table widget; documents how parameter/search syntax works (widget bindings, optional date range). Use when the user asks to add a query, connect a query to a table, create a new query, or how parameters/filters work in queries.
---
# Add Query to Table & Parameter Search Syntax
Use this skill when adding a new query to a page, wiring a query to a table, or when you need the correct syntax for parameterized filters (e.g. date range from widgets).
## 1. Query structure (single source of truth)
Each query lives under the page in a folder named after the query:
- **Path**: `pages/<PageName>/queries/<query_name>/`
- **Files**:
- **`<query_name>.txt`** — Raw SQL. This is the **source of truth** for the query body. Keep it readable (real newlines, no escaping).
- **`metadata.json`** — Appsmith action config. The `body` inside `unpublishedAction.actionConfiguration` must match the SQL in the `.txt` file, stored as a single-line string with `\n` for newlines and `\"` for double quotes.
**Naming**: Use lowercase with underscores (e.g. `units_ordered_by_series`, `units_shipped_by_power_input`). The query name is the identifier used in bindings (e.g. `{{query_name.data}}`).
## 2. Metadata.json required fields
- **`id`**: `"<PageName>_<query_name>"` (e.g. `"Sales - Units Ordered_units_ordered_by_series"`).
- **`name`**: `"<query_name>"` (must match the folder and the name used in bindings).
- **`unpublishedAction.actionConfiguration.body`**: The exact SQL from `<query_name>.txt`, escaped for JSON (newlines → `\n`, `"``\"`).
- **`unpublishedAction.datasource`**: Use the same as other queries on the page (e.g. `xTuple_Sandbox`).
- **`unpublishedAction.pageId`**: `"<PageName>"`.
- **`unpublishedAction.dynamicBindingPathList`**: `[{"key":"body"}]` when the body contains `{{...}}` widget references.
- Keep **`pluginId`**: `"postgres-plugin"`, **`pluginType`**: `"DB"`, and existing flags like **`encodeParamsToggle`**, **`paginationType`**, **`pluginSpecifiedTemplates`**, **`timeoutInMillisecond`** consistent with other DB queries in the app.
When adding a new query, copy an existing querys `metadata.json` from the same page and change `id`, `name`, and `body` (and ensure `body` stays in sync with the new `.txt` file).
## 3. Wiring the query to a Table widget
- In the Table widget JSON (e.g. `widgets/.../Table1.json`), set:
- **`tableData`**: `"{{<query_name>.data}}"`
- Table columns read from the query result by **key**. The key is the **SELECT alias** from the query (e.g. `"Product"`, `"Qty Ordered"`). In `primaryColumns`, the column **id** can be a safe identifier (e.g. `Qty_Ordered`) while **originalId** / **alias** / **label** match the display name; **computedValue** must use the same key as in the query result, e.g. `currentRow["Qty Ordered"]`.
So: **query SELECT aliases = keys in the tables row object**. Keep column keys and any `currentRow["..."]` references in the table in sync with those aliases.
## 4. Parameter search syntax (widgets in SQL)
### 4.1 Referencing a widget value
- **Syntax**: `{{WidgetName.property}}`
- **Examples**:
- DatePicker date: `{{SeriesDateFrom.selectedDate}}`, `{{PowerDateTo.selectedDate}}`
- Other widgets: use the widgets value property (e.g. `selectedOptionValue`, `text`) as per Appsmith docs.
Values are injected as strings. For PostgreSQL dates you typically cast in SQL, e.g. `'{{DatePicker1.selectedDate}}'::date`.
### 4.2 Optional date range (no filter when either date is empty)
Use this pattern so that:
- If **either** date widget is empty → the date condition is **not** applied (all dates allowed).
- If **both** are set → filter by `date_column BETWEEN from ::date AND to ::date`.
**SQL pattern** (replace widget names and column as needed):
```sql
AND (
NULLIF('{{DateFromWidget.selectedDate}}','') IS NULL
OR NULLIF('{{DateToWidget.selectedDate}}','') IS NULL
OR date_column BETWEEN
NULLIF('{{DateFromWidget.selectedDate}}','')::date
AND NULLIF('{{DateToWidget.selectedDate}}','')::date
)
```
- **Logic**: `NULLIF('{{...}}','')` turns an empty string into SQL `NULL`. If either widget is empty, one of the first two conditions is true, so the whole `AND (...)` is true and the BETWEEN is not applied. When both are non-empty, the third branch applies the range.
- **Widget names**: Use the actual widget names (e.g. `SeriesDateFrom` / `SeriesDateTo` for one tab, `PowerDateFrom` / `PowerDateTo` for another). Ensure those widgets exist on the same page and (if in a tab) the same tab so the query runs with the right context.
### 4.3 Required parameters (always filter)
If the filter must always be applied (no “show all” when empty):
- Use the binding directly and ensure the widget always has a value, or use a default in the widget.
- Example: `AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date` — then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values.
## 5. Checklist when adding a new query
1. Create `pages/<PageName>/queries/<query_name>/`.
2. Add `<query_name>.txt` with the full SQL (source of truth).
3. Add `metadata.json` with correct `id`, `name`, `body` (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries.
4. If the SQL uses `{{...}}`, set `dynamicBindingPathList` to `[{"key":"body"}]`.
5. In the Table widget that should show the data, set `tableData` to `{{<query_name>.data}}`.
6. Ensure table column keys / `currentRow["..."]` match the querys SELECT aliases.
## 6. Renaming or replacing a query
- To **rename** (e.g. `units_shipped_by_series``units_ordered_by_series`):
- Create the new folder and files under the new name.
- Update every reference to the old query (e.g. `tableData`: `{{old_name.data}}``{{new_name.data}}`).
- Remove the old query folder (both `.txt` and `metadata.json`).
- When **replacing** the SQL but keeping the same name, update the `.txt` first, then update the `body` in `metadata.json` to match (same content, JSON-escaped).
## Reference
- **Optional date range**: NULLIF + IS NULL + OR + BETWEEN as above.
- **Table data binding**: `{{<query_name>.data}}`.
- **Column keys**: Must match query SELECT aliases (e.g. `"Product"`, `"Qty Ordered"`).