@querypanel/node-sdk 1.0.41 → 1.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,18 +42,26 @@ const createPostgresClient = () => async (sql: string, params?: unknown[]) => {
42
42
  }
43
43
  };
44
44
 
45
- qp.attachPostgres("analytics", createPostgresClient(), {
46
- description: "Primary analytics warehouse",
47
- tenantFieldName: "tenant_id",
48
- });
45
+ // Attach PostgreSQL database using the SDK's PostgresAdapter
46
+ // The SDK will automatically handle tenant isolation when tenantFieldName is provided
47
+ qp.attachPostgres(
48
+ "pg_demo", // a uniq identifier for QueryPanel
49
+ createPostgresClientFn(),
50
+ {
51
+ database: "pg_demo", // database name
52
+ description: "PostgreSQL demo database", // some description that QueryPanel can use
53
+ tenantFieldName: "tenant_id", // SDK will automatically filter by tenant_id
54
+ enforceTenantIsolation: true, // Ensures all queries include tenant_id filter
55
+ allowedTables: ["orders"], // Only sync 'orders' table - 'users' will be excluded
56
+ });
49
57
 
50
58
  qp.attachClickhouse(
51
- "clicks",
59
+ "clicks", // uniq identifier for QueryPanel
52
60
  (params) => clickhouse.query(params),
53
61
  {
54
- database: "analytics",
55
- tenantFieldName: "customer_id",
56
- tenantFieldType: "String",
62
+ database: "analytics", // database name
63
+ tenantFieldName: "customer_id", // SDK will automatically filter by tenant_id
64
+ tenantFieldType: "String", // SDK will use it in the clickhouse query as {customer_id::String}
57
65
  },
58
66
  );
59
67
 
@@ -106,6 +114,133 @@ if (response.chart.vegaLiteSpec) {
106
114
  const charts = await qp.listCharts({ tenantId: "tenant_123" });
107
115
  ```
108
116
 
117
+ ## Modifying Charts
118
+
119
+ The `modifyChart()` method allows you to edit SQL and/or visualization settings, then re-execute and regenerate charts. It works with both fresh `ask()` responses and saved charts.
120
+
121
+ ### Changing Visualization Settings
122
+
123
+ Modify chart type, axes, or series without regenerating SQL:
124
+
125
+ ```ts
126
+ // Start with an ask() response
127
+ const response = await qp.ask("revenue by country", {
128
+ tenantId: "tenant_123",
129
+ database: "analytics",
130
+ });
131
+
132
+ // Change to a bar chart with specific axis configuration
133
+ const modified = await qp.modifyChart({
134
+ sql: response.sql,
135
+ question: "revenue by country",
136
+ database: "analytics",
137
+ vizModifications: {
138
+ chartType: "bar",
139
+ xAxis: { field: "country", label: "Country" },
140
+ yAxis: { field: "revenue", label: "Total Revenue", aggregate: "sum" },
141
+ },
142
+ }, { tenantId: "tenant_123" });
143
+
144
+ console.log(modified.chart); // New chart spec with bar visualization
145
+ console.log(modified.modified.vizChanged); // true
146
+ console.log(modified.modified.sqlChanged); // false
147
+ ```
148
+
149
+ ### Changing Time Granularity and Date Range
150
+
151
+ These modifications trigger SQL regeneration:
152
+
153
+ ```ts
154
+ // Change from daily to monthly aggregation
155
+ const monthly = await qp.modifyChart({
156
+ sql: response.sql,
157
+ question: "revenue over time",
158
+ database: "analytics",
159
+ sqlModifications: {
160
+ timeGranularity: "month",
161
+ dateRange: { from: "2024-01-01", to: "2024-12-31" },
162
+ },
163
+ }, { tenantId: "tenant_123" });
164
+
165
+ console.log(monthly.sql); // New SQL with monthly GROUP BY
166
+ console.log(monthly.modified.sqlChanged); // true
167
+ ```
168
+
169
+ ### Direct SQL Editing
170
+
171
+ Provide custom SQL that will be executed directly:
172
+
173
+ ```ts
174
+ const customized = await qp.modifyChart({
175
+ sql: response.sql,
176
+ question: "revenue by country",
177
+ database: "analytics",
178
+ sqlModifications: {
179
+ customSql: `
180
+ SELECT country, SUM(revenue) as total_revenue
181
+ FROM orders
182
+ WHERE status = 'completed' AND created_at > '2024-01-01'
183
+ GROUP BY country
184
+ ORDER BY total_revenue DESC
185
+ LIMIT 10
186
+ `,
187
+ },
188
+ }, { tenantId: "tenant_123" });
189
+
190
+ // Optionally save the modified chart
191
+ if (customized.chart.vegaLiteSpec) {
192
+ await qp.createChart({
193
+ title: "Top 10 Countries by Revenue (Completed Orders)",
194
+ sql: customized.sql,
195
+ sql_params: customized.params,
196
+ vega_lite_spec: customized.chart.vegaLiteSpec,
197
+ target_db: customized.target_db,
198
+ }, { tenantId: "tenant_123" });
199
+ }
200
+ ```
201
+
202
+ ### Combining SQL and Visualization Changes
203
+
204
+ Apply both types of modifications in a single call:
205
+
206
+ ```ts
207
+ const combined = await qp.modifyChart({
208
+ sql: response.sql,
209
+ question: "revenue over time",
210
+ database: "analytics",
211
+ sqlModifications: {
212
+ timeGranularity: "week",
213
+ additionalInstructions: "exclude refunded orders",
214
+ },
215
+ vizModifications: {
216
+ chartType: "area",
217
+ stacking: "stacked",
218
+ },
219
+ }, { tenantId: "tenant_123" });
220
+ ```
221
+
222
+ ### Modifying Saved Charts
223
+
224
+ Load a saved chart and modify it:
225
+
226
+ ```ts
227
+ // Load a saved chart
228
+ const savedChart = await qp.getChart("chart_id_123", {
229
+ tenantId: "tenant_123",
230
+ });
231
+
232
+ // Modify it
233
+ const modified = await qp.modifyChart({
234
+ sql: savedChart.sql,
235
+ question: "original question", // Store this when saving charts
236
+ database: savedChart.target_db ?? "analytics",
237
+ params: savedChart.sql_params as Record<string, unknown>,
238
+ vizModifications: {
239
+ chartType: "line",
240
+ },
241
+ }, { tenantId: "tenant_123" });
242
+ ```
243
+
109
244
  ## Building a Dashboard (Active Charts)
110
245
 
111
246
  While `createChart` and `listCharts` manage your **history** of saved queries, "Active Charts" are designed for building **dashboards**. You can "pin" a saved chart to a dashboard, control its order, and fetch it with live data in a single call.