@graphenedata/cli 0.0.5 → 0.0.7

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.
@@ -21,6 +21,10 @@ Graphene also has a CLI that lets you check syntax, run queries, serve data apps
21
21
  - [Using stored expressions in queries](#using-stored-expressions-in-queries)
22
22
  - [Safe aggregation in fan-outs](#safe-aggregation-in-fan-outs)
23
23
  - [`table as` statements](#table-as-statements)
24
+ - [`extend` statements](#extend-statements)
25
+ - [Working with dates, timestamps, and intervals](#working-with-dates-timestamps-and-intervals)
26
+ - [Date and timestamp literals](#date-and-timestamp-literals)
27
+ - [Interval literals](#interval-literals)
24
28
  - [Other miscellaneous details about GSQL](#other-miscellaneous-details-about-gsql)
25
29
  - [Graphene data apps (dashboards)](#graphene-data-apps-dashboards)
26
30
  - [Visualization components](#visualization-components)
@@ -90,40 +94,30 @@ GSQL is comprised of `table` statements that declare tables and `select` stateme
90
94
 
91
95
  ```sql
92
96
  table orders (
93
-
94
- /* Base columns */
95
-
96
- id BIGINT primary_key,
97
- user_id BIGINT,
98
- created_at DATETIME,
99
- status STRING, -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
100
- amount FLOAT, -- Amount paid by customer
101
- cost FLOAT, -- Cost of materials
102
-
103
- /* Join relationships */
104
-
105
- join_one users on user_id = users.id,
106
-
107
- /* Scalar expressions */
108
-
109
- status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
110
-
111
- /* Agg expressions */
112
-
113
- sum(case when revenue_recognized then amount else 0 end) as revenue,
114
- sum(case when revenue_recognized then cost else 0 end) as cogs,
115
- revenue - cogs as profit,
116
- profit / revenue as profit_margin
97
+ id BIGINT primary_key
98
+ user_id BIGINT
99
+ created_at DATETIME
100
+ status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
101
+ amount FLOAT -- Amount paid by customer
102
+ cost FLOAT -- Cost of materials
103
+
104
+ join one users on user_id = users.id
105
+
106
+ revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
107
+ revenue: sum(case when revenue_recognized then amount else 0 end)
108
+ cogs: sum(case when revenue_recognized then cost else 0 end)
109
+ profit: revenue - cogs
110
+ profit_margin: profit / revenue
117
111
  )
118
112
 
119
113
  table users (
120
- id BIGINT primary_key,
121
- name VARCHAR,
122
- email VARCHAR,
123
- age INTEGER,
124
- country_code VARCHAR,
114
+ id BIGINT primary_key
115
+ name VARCHAR
116
+ email VARCHAR
117
+ age INTEGER
118
+ country_code VARCHAR
125
119
 
126
- join_many orders on id = orders.user_id
120
+ join many orders on id = orders.user_id
127
121
  )
128
122
  ```
129
123
 
@@ -154,19 +148,19 @@ Sometimes there are multiple valid ways to join two tables together. You can mod
154
148
  ```sql
155
149
  table projects (
156
150
  ...
157
- owner_id BIGINT,
158
- viewer_id BIGINT,
151
+ owner_id BIGINT
152
+ viewer_id BIGINT
159
153
 
160
- join_one users as project_owner on owner_id = project_owner.id,
161
- join_one users as project_viewer on viewer_id = project_viewer.id
154
+ join one users as project_owner on owner_id = project_owner.id
155
+ join one users as project_viewer on viewer_id = project_viewer.id
162
156
  )
163
157
 
164
158
  table users (
165
159
  ...
166
- id BIGINT,
160
+ id BIGINT
167
161
 
168
- join_many projects as projects_as_owner on id = projects_as_owner.owner_id,
169
- join_many projects as projects_as_viewer on id = projects_as_viewer.viewer_id
162
+ join many projects as projects_as_owner on id = projects_as_owner.owner_id
163
+ join many projects as projects_as_viewer on id = projects_as_viewer.viewer_id
170
164
  )
171
165
  ```
172
166
 
@@ -179,7 +173,7 @@ table users (
179
173
 
180
174
  **Stored expressions** are GSQL expressions (ie. any arbitrary combination of functions, operators, and column references) that you want to make reusable to queries. Stored expressions are great for canonizing metrics, segments, and other important business definitions.
181
175
 
182
- A stored expression must be given a name via `as`. It can then be referenced by name in queries that use the parent table. See [Using stored expressions in queries](#using-stored-expressions-in-queries) below for how to use stored expressions in queries.
176
+ A stored expression must be given a name via `name: expression`. It can then be referenced by name in queries that use the parent table. See [Using stored expressions in queries](#using-stored-expressions-in-queries) below for how to use stored expressions in queries.
183
177
 
184
178
  Like expressions in regular SQL, expressions in GSQL are either scalar or aggregative. In BI parlance, these would be called dimensions and measures, respectively.
185
179
 
@@ -190,15 +184,13 @@ table orders (
190
184
  ...
191
185
 
192
186
  /* Scalar expressions */
193
-
194
- status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
187
+ revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
195
188
 
196
189
  /* Agg expressions */
197
-
198
- sum(case when revenue_recognized then amount else 0 end) as revenue,
199
- sum(case when revenue_recognized then cost else 0 end) as cogs,
200
- revenue - cogs as profit, -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
201
- profit / revenue as profit_margin
190
+ revenue: sum(case when revenue_recognized then amount else 0 end)
191
+ cogs: sum(case when revenue_recognized then cost else 0 end)
192
+ profit: revenue - cogs -- even though there are no agg functions here, this is still aggregative as it references other aggregative expressions
193
+ profit_margin: profit / revenue
202
194
  )
203
195
  ```
204
196
 
@@ -221,7 +213,7 @@ If you recall the model from before:
221
213
  table orders (
222
214
  ...
223
215
  user_id BIGINT,
224
- join_one users on user_id = users.id
216
+ join one users on user_id = users.id
225
217
  )
226
218
 
227
219
  table users (
@@ -253,23 +245,23 @@ Sometimes you need to access columns or stored expressions in a table that is tw
253
245
  table orders (
254
246
  ...
255
247
 
256
- join_one users on user_id = users.id
248
+ join one users on user_id = users.id
257
249
  )
258
250
 
259
251
  table users (
260
252
  ...
261
253
 
262
- join_many orders on id = orders.user_id,
263
- join_one country on country_code = countries.code
254
+ join many orders on id = orders.user_id
255
+ join one country on country_code = countries.code
264
256
  )
265
257
 
266
258
  table countries (
267
- code VARCHAR primary_key,
268
- name VARCHAR,
269
- currency VARCHAR,
270
- free_shipping BOOLEAN,
259
+ code VARCHAR primary_key
260
+ name VARCHAR
261
+ currency VARCHAR
262
+ free_shipping BOOLEAN
271
263
 
272
- join_many users on code = users.country_code
264
+ join many users on code = users.country_code
273
265
  )
274
266
  ```
275
267
 
@@ -294,21 +286,20 @@ Again, using the orders table from before:
294
286
 
295
287
  ```sql
296
288
  table orders (
297
- id BIGINT primary_key,
298
- user_id BIGINT,
299
- created_at DATETIME,
300
- status STRING, -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
301
- amount FLOAT, -- Amount paid by customer
302
- cost FLOAT, -- Cost of materials
303
-
304
- join_one users on user_id = users.id,
305
-
306
- status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
307
-
308
- sum(case when revenue_recognized then amount else 0 end) as revenue,
309
- sum(case when revenue_recognized then cost else 0 end) as cogs,
310
- revenue - cogs as profit,
311
- profit / revenue as profit_margin
289
+ id BIGINT primary_key
290
+ user_id BIGINT
291
+ created_at DATETIME
292
+ status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
293
+ amount FLOAT -- Amount paid by customer
294
+ cost FLOAT -- Cost of materials
295
+
296
+ join one users on user_id = users.id
297
+
298
+ revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
299
+ revenue: sum(case when revenue_recognized then amount else 0 end)
300
+ cogs: sum(case when revenue_recognized then cost else 0 end)
301
+ profit: revenue - cogs
302
+ profit_margin: profit / revenue
312
303
  )
313
304
  ```
314
305
 
@@ -330,7 +321,7 @@ select
330
321
  status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
331
322
  count(*)
332
323
  from orders
333
- group by 1
324
+ group by 1
334
325
  ```
335
326
 
336
327
  You can see that invoking a stored expression is like using a macro: the definition for the stored expression is effectively expanded in-line by Graphene when it runs the query.
@@ -371,7 +362,7 @@ GSQL aims to solve this problem. With the additional information provided via `j
371
362
  The query `select avg(users.age) from orders` will be rewritten to the following SQL when Graphene queries the underlying database (this is for BigQuery, specifically):
372
363
 
373
364
  ```sql
374
- SELECT
365
+ SELECT
375
366
  (CAST((
376
367
  (
377
368
  SUM(DISTINCT
@@ -388,51 +379,48 @@ FROM `bigquery-public-data.thelook_ecommerce.orders` as base
388
379
 
389
380
  You don't have to understand this; the point is that GSQL is minimizing the chances that naive users aggregate data incorrectly.
390
381
 
382
+ #### Percentile shorthand
383
+
384
+ Graphene provides percentile helpers so you rarely have to remember the SQL form for each warehouse. Anywhere you can call an aggregate, you can also write `pXX(column)` where `XX` is a whole number between 0 and 100. If you need precision finer than a whole percentile, append extra digits—everything after the first two digits is treated as decimals. Examples: `p975` → 97.5th percentile, `p9999` → 99.99th percentile. Graphene rewrites these shorthands to the dialect’s native function (`quantile_cont` on DuckDB, `approx_quantiles` on BigQuery, `PERCENTILE_CONT` on Snowflake) and ensures they behave like other aggregates (automatic grouping, structPath handling, etc.).
385
+
391
386
  ### `table as` statements
392
387
 
393
388
  You can turn the output of any `select` statement into a table with `table foo as (select ...)`. Here's an example of an additional table `user_facts` added to the two tables from earlier:
394
389
 
395
390
  ```sql
396
391
  table orders (
397
- id BIGINT primary_key,
398
- user_id BIGINT,
399
- created_at DATETIME,
400
- status STRING, -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
401
- amount FLOAT, -- Amount paid by customer
402
- cost FLOAT, -- Cost of materials
403
-
404
- join_one users on user_id = users.id,
405
-
406
- status in ('Processing', 'Shipped', 'Complete') as revenue_recognized,
407
-
408
- sum(case when revenue_recognized then amount else 0 end) as revenue,
409
- sum(case when revenue_recognized then cost else 0 end) as cogs,
410
- revenue - cogs as profit,
411
- profit / revenue as profit_margin
392
+ id BIGINT primary_key
393
+ user_id BIGINT
394
+ created_at DATETIME
395
+ status STRING -- One of 'Processing', 'Shipped', 'Complete', 'Cancelled', 'Returned'
396
+ amount FLOAT -- Amount paid by customer
397
+ cost FLOAT -- Cost of materials
398
+
399
+ join one users on user_id = users.id
400
+
401
+ revenue_recognized: status in ('Processing', 'Shipped', 'Complete')
402
+ revenue: sum(case when revenue_recognized then amount else 0 end)
403
+ cogs: sum(case when revenue_recognized then cost else 0 end)
404
+ profit: revenue - cogs
405
+ profit_margin: profit / revenue
412
406
  )
413
407
 
414
408
  table users (
415
- id BIGINT primary_key,
416
- name VARCHAR,
417
- email VARCHAR,
418
- age INTEGER,
419
-
420
- join_many orders on id = orders.user_id,
421
- join_one user_facts on id = user_facts.id,
409
+ id BIGINT primary_key
410
+ name VARCHAR
411
+ email VARCHAR
412
+ age INTEGER
422
413
 
423
- /* Scalar expressions */
414
+ join many orders on id = orders.user_id
415
+ join one user_facts on id = user_facts.id
424
416
 
425
- user_facts.ltv as ltv,
426
- user_facts.lifetime_orders as lifetime_orders
417
+ ltv: user_facts.ltv
418
+ lifetime_orders: user_facts.lifetime_orders
427
419
  )
428
420
 
429
421
  table user_facts as (
430
- select
431
- id,
432
- orders.revenue as ltv,
433
- count(orders.id) as lifetime_orders,
434
- from users
435
- group by id
422
+ select id, orders.revenue as ltv, count(orders.id) as lifetime_orders,
423
+ from users group by id
436
424
  )
437
425
  ```
438
426
 
@@ -440,6 +428,61 @@ table user_facts as (
440
428
  - You cannot yet declare join relationships or stored expressions directly in a `table as` statement. Other tables can declare join relationships to it, though, as shown above.
441
429
  - In the example above, the `ltv` and `lifetime_orders` columns from `user_facts` are "hoisted" back into `users` so that they appear as if they are columns from `users`. This is simply a design choice which allows query writers to never need to know about `user_facts`.
442
430
 
431
+ ### `extend` statements
432
+
433
+ `extend` statements allow you to add join relationships or stored expressions to an existing table. This is especially useful for tables created via `table as` statements, which do not support defining these properties directly.
434
+
435
+ For example, if we have a `table as` statement that creates a daily summary of orders:
436
+
437
+ ```sql
438
+ table daily_orders as (
439
+ select
440
+ date_trunc(created_at, day) as day,
441
+ count(*) as num_orders,
442
+ sum(amount) as total_revenue
443
+ from orders
444
+ group by 1
445
+ )
446
+ ```
447
+
448
+ We can extend this table to add measures or joins:
449
+
450
+ ```sql
451
+ extend daily_orders (
452
+ join one calendar on day = calendar.date
453
+
454
+ avg_order_value: total_revenue / num_orders
455
+ )
456
+ ```
457
+
458
+ Note that you cannot add new base columns with `extend`; you can only add joins and stored expressions.
459
+
460
+ ### Working with dates, timestamps, and intervals
461
+
462
+ Graphene understands a handful of common literal formats so you rarely need explicit casts when filtering or doing time math.
463
+
464
+ **Date and timestamp literals**
465
+
466
+ - `YYYY`, `YYYY-MM`, and `YYYY-MM-DD` strings are treated as dates. Leading/trailing spaces are ignored.
467
+ - `YYYY-MM-DD HH[:MM[:SS]]` (with either a space or `T` between the date and time) is treated as a timestamp. Missing minutes or seconds default to `00`.
468
+
469
+ ```sql
470
+ from users select id
471
+ where created_at >= '2024-01-01' and created_at <= '2024-02-01'
472
+ ```
473
+
474
+ **Interval literals**
475
+
476
+ To add or subtract time, provide a quantity followed by a unit inside a string literal. Supported units include `second`, `minute`, `hour`, `day`, `week`, `month`, `quarter`, and `year` (plural forms or shorthands like `secs`, `mins`, `hrs` also work).
477
+
478
+ ```sql
479
+ from users select
480
+ created_at + '5 minutes' as first_seen_plus_5,
481
+ created_at - '2 days' as first_seen_minus_2
482
+ ```
483
+
484
+ Interval literals accept decimals (`'1.5 hours'`) and negative values (`'-7 days'`). Invalid strings produce a diagnostic such as “Could not parse interval literal: "many moons"”.
485
+
443
486
  ### Other miscellaneous details about GSQL
444
487
 
445
488
  - Trailing commas in `table` statements are optional.
@@ -449,6 +492,16 @@ table user_facts as (
449
492
  - Expressions in `group by` are implicitly selected, so `from orders select avg(amount) group by user_id` will return two columns.
450
493
  - `count` is a reserved word. Do not alias your columns as `count`.
451
494
  - Window functions and set operations are not supported.
495
+ - Subqueries are not supported. However, you can accomplish the same functionality by chaining queries:
496
+ ````md
497
+ ```sql my_subquery
498
+ select ...
499
+ ```
500
+
501
+ ```sql my_query
502
+ select ... from my_subquery
503
+ ```
504
+ ````
452
505
 
453
506
  ## Graphene data apps (dashboards)
454
507
 
@@ -493,7 +546,7 @@ Use bar or column charts to compare a metric across categories. Bar charts are b
493
546
  Here's an example:
494
547
 
495
548
  ```markdown
496
- <BarChart
549
+ <BarChart
497
550
  title="Sales by Category"
498
551
  data=orders_by_category_2021
499
552
  x=month
@@ -522,10 +575,10 @@ Here's an example:
522
575
  |----------|-------------|----------|---------|---------|
523
576
  | data | Query name, wrapped in curly braces | true | query name | - |
524
577
  | x | Column or expression to use for the x-axis of the chart | false | column name, stored expression name, GSQL expression | First column |
525
- | y | Column(s) or expression(s) to use for the y-axis of the chart | false | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
526
- | y2 | Column(s) or expression(s) to include on a secondary y-axis | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
578
+ | y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | false | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
579
+ | y2 | Column(s) or expression(s) to include on a secondary y-axis. | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
527
580
  | y2SeriesType | Chart type to apply to the series on the y2 axis | false | `bar`, `line`, `scatter` | `bar` |
528
- | series | Column or expression to use as the series (groups) in a multi-series chart | false | column name, stored expression name, GSQL expression | - |
581
+ | series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
529
582
  | sort | Whether to apply default sort to your data. Default sort is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
530
583
  | type | Grouping method to use for multi-series charts | false | `stacked`, `grouped`, `stacked100` | `stacked` |
531
584
  | stackName | Name for an individual stack. If separate Bar components are used with different stackNames, the chart will show multiple stacks | false | string | - |
@@ -608,7 +661,7 @@ Use a pie chart to show part-to-whole relationships across categories. Best for
608
661
  Here's an example:
609
662
 
610
663
  ```markdown
611
- <PieChart
664
+ <PieChart
612
665
  title="Sales share by category"
613
666
  data=orders_by_category_2021
614
667
  category=category
@@ -640,12 +693,12 @@ Use line charts to display how one or more metrics vary over time. Line charts a
640
693
  Here's an example:
641
694
 
642
695
  ```markdown
643
- <LineChart
696
+ <LineChart
644
697
  title="Monthly Sales"
645
698
  subtitle="Includes all categories"
646
699
  data=orders_by_month
647
700
  x=month
648
- y=sales_usd0k
701
+ y=sales_usd0k
649
702
  yAxisTitle="Sales per Month"
650
703
  />
651
704
  ```
@@ -670,10 +723,10 @@ Here's an example:
670
723
  |------|-------------|----------|---------|---------|
671
724
  | data | Query name, wrapped in curly braces | true | query name | - |
672
725
  | x | Column or expression to use for the x-axis of the chart | true | column name, stored expression name, GSQL expression | - |
673
- | y | Column(s) or expression(s) to use for the y-axis of the chart | true | column name, stored expression name, GSQL expression, list of any combination of these | - |
674
- | y2 | Column(s) or expression(s) to include on a secondary y-axis | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
726
+ | y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | true | column name, stored expression name, GSQL expression, list of any combination of these | - |
727
+ | y2 | Column(s) or expression(s) to include on a secondary y-axis. | false | column name, stored expression name, GSQL expression, list of any combination of these | - |
675
728
  | y2SeriesType | Chart type to apply to the series on the y2 axis | false | `line`, `bar`, `scatter` | `line` |
676
- | series | Column or expression to use as the series (groups) in a multi-series chart | false | column name, stored expression name, GSQL expression | - |
729
+ | series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
677
730
  | sort | Whether to apply default sort to your data. Default is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
678
731
  | handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` |
679
732
  | emptySet | Sets behaviour for empty datasets. Can throw an error, a warning, or allow empty. When set to 'error', empty datasets will block builds in `build:strict`. Note this only applies to initial page load - empty datasets caused by input component changes (dropdowns, etc.) are allowed. | false | `error`, `warn`, `pass` | `error` |
@@ -752,7 +805,7 @@ Use area charts to track how a metric with multiple series changes over time, or
752
805
  Here's an example:
753
806
 
754
807
  ```markdown
755
- <AreaChart
808
+ <AreaChart
756
809
  data=orders_by_month
757
810
  x=month
758
811
  y=sales
@@ -779,8 +832,8 @@ Here's an example:
779
832
  |------|-------------|----------|---------|---------|
780
833
  | data | Query name, wrapped in curly braces | true | query name | - |
781
834
  | x | Column or expression to use for the x-axis of the chart | true | column name, stored expression name, GSQL expression | First column |
782
- | y | Column(s) or expression(s) to use for the y-axis of the chart | true | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
783
- | series | Column or expression to use as the series (groups) in a multi-series chart | false | column name, stored expression name, GSQL expression | - |
835
+ | y | Column(s) or expression(s) to use for the y-axis of the chart. Each will create its own series. Consider a split axis with `y2` if there is a difference of scale or unit of measure between the series. | true | column name, stored expression name, GSQL expression, list of any combination of these | Any non-assigned numeric columns |
836
+ | series | Column or expression to use to define the series (groups) in a multi-series chart. Use when values of a particular column dictate the multiple series to plot, eg. `country` would create a series for every distinct country in the column. | false | column name, stored expression name, GSQL expression | - |
784
837
  | sort | Whether to apply default sort to your data. Default sort is x ascending for number and date x-axes, and y descending for category x-axes | false | `true`, `false` | `true` |
785
838
  | type | Grouping method to use for multi-series charts | false | `stacked`, `stacked100` | `stacked` |
786
839
  | handleMissing | Treatment of missing values in the dataset | false | `gap`, `connect`, `zero` | `gap` for single series, `zero` for multi-series |
@@ -851,8 +904,8 @@ Use big values to display a large value standalone, and optionally include a com
851
904
  Here's an example:
852
905
 
853
906
  ```markdown
854
- <BigValue
855
- data=orders_with_comparisons
907
+ <BigValue
908
+ data=orders_with_comparisons
856
909
  value=num_orders
857
910
  sparkline=month
858
911
  comparison=order_growth
@@ -1069,7 +1122,7 @@ The user-inputted text would then be referenced in GSQL via `$name_of_input`. Fo
1069
1122
  ```sql
1070
1123
  select *
1071
1124
  from users
1072
- where email ilike concat('%', $name_of_input, '%')
1125
+ where email ilike concat('%', $name_of_input, '%')
1073
1126
  ```
1074
1127
 
1075
1128
  ##### All text input attributes
@@ -1083,44 +1136,6 @@ where email ilike concat('%', $name_of_input, '%')
1083
1136
  | description | Adds an info icon with description tooltip on hover | false | string | - |
1084
1137
 
1085
1138
 
1086
- #### Date range
1087
-
1088
- Creates a date picker that can be used to filter a query. Includes a set of preset ranges for quick selection of common date ranges (relative to the supplied end date). To see how to filter a query using an input component, see Filters.
1089
-
1090
- Here's an example:
1091
-
1092
- ```markdown
1093
- <DateRange
1094
- name=date_range_name
1095
- data=orders_by_day
1096
- dates=day
1097
- />
1098
- ```
1099
-
1100
- The start and end dates for the user-selected range would then be referenced in GSQL as `$date_range_name_start` and `$date_range_name_end` at the end. For example:
1101
-
1102
- ```sql
1103
- select *
1104
- from orders
1105
- where created_at > $date_range_name_start and < $date_range_name_end
1106
- ```
1107
-
1108
- ##### All date range attributes
1109
-
1110
- | Attribute | Description | Required | Options | Default |
1111
- |------|-------------|----------|---------|---------|
1112
- | name | Name of the DateRange, used to reference the selected values elsewhere as `"$name_start"` or `"$name_end"` | true | string | - |
1113
- | data | Query name, wrapped in curly braces | false | query name | - |
1114
- | dates | Column or expression from the query containing date range to span | false | column name, stored expression name, GSQL expression | - |
1115
- | start | A manually specified start date to use for the range | false | string formatted YYYY-MM-DD | - |
1116
- | end | A manually specified end date to use for the range | false | string formatted YYYY-MM-DD | - |
1117
- | title | Title to display in the Date Range component | false | string | - |
1118
- | presetRanges | Customize "Select a Range" drop down, by including preset range options | false | list of values e.g. `"Last 7 Days, Last 30 Days"`. Allowed values: `Last 7 Days`, `Last 30 Days`, `Last 90 Days`, `Last 365 Days`, `Last 3 Months`, `Last 6 Months`, `Last 12 Months`, `Last Month`, `Last Year`, `Month to Date`, `Month to Today`, `Year to Date`, `Year to Today`, `All Time` | - |
1119
- | defaultValue | Accepts preset in string format to apply default value in Date Range picker | false | `"Last 7 Days"`, `"Last 30 Days"`, `"Last 90 Days"`, `"Last 365 Days"`, `"Last 3 Months"`, `"Last 6 Months"`, `"Last 12 Months"`, `"Last Month"`, `"Last Year"`, `"Month to Date"`, `"Month to Today"`, `"Year to Date"`, `"Year to Today"`, `"All Time"` | - |
1120
- | hideDuringPrint | Hide the component when the report is printed | false | `true`, `false` | `true` |
1121
- | description | Adds an info icon with description tooltip on hover | false | string | - |
1122
-
1123
-
1124
1139
  #### Dropdown
1125
1140
 
1126
1141
  Creates a dropdown menu with a list of options that can be selected. The selected option can be used to filter queries or in markdown. To see how to filter a query using a dropdown, see Filters.
@@ -1132,10 +1147,10 @@ Here's an example:
1132
1147
  select distinct status from orders
1133
1148
  ```
1134
1149
 
1135
- <Dropdown
1136
- title="Select Order Status"
1150
+ <Dropdown
1151
+ title="Select Order Status"
1137
1152
  name="status_dropdown"
1138
- data="statuses"
1153
+ data="statuses"
1139
1154
  value="status"
1140
1155
  defaultValue="Complete"
1141
1156
  />
@@ -1201,20 +1216,20 @@ The easiest way to format numbers and dates in Graphene is through component att
1201
1216
  For example, you can use the `fmt` attribute to format values inside a BigValue component:
1202
1217
 
1203
1218
  ```markdown
1204
- <BigValue
1205
- data=sales_data
1206
- value=sales
1207
- fmt="$#,##0"
1219
+ <BigValue
1220
+ data=sales_data
1221
+ value=sales
1222
+ fmt="$#,##0"
1208
1223
  />
1209
1224
  ```
1210
1225
 
1211
1226
  Within charts, you can format individual columns using `xFmt` and `yFmt`:
1212
1227
 
1213
1228
  ```markdown
1214
- <LineChart
1215
- data=sales_data
1216
- x=date
1217
- y=sales
1229
+ <LineChart
1230
+ data=sales_data
1231
+ x=date
1232
+ y=sales
1218
1233
  xFmt="m/d"
1219
1234
  yFmt=eur
1220
1235
  />
@@ -1306,8 +1321,6 @@ These are the available commands:
1306
1321
  - `npm run graphene check <mdPath> --chart "<chartTitle>"` - Same as above, except if the runtime check is successful, only takes a screenshot of the specified chart. `<chartTitle>` must match (case sensitive) the `title` attribute on the chart component. `-c` can be used as shorthand for `--chart`.
1307
1322
  - `npm run graphene compile "<GSQL>"` - Shows how GSQL is translated into the underlying database SQL.
1308
1323
  - `npm run graphene run "<GSQL>"` - Runs a GSQL query. The tables and semantics defined in all .gsql files in the project are available for the query to use.
1309
- - `npm run graphene serve` - Starts (or restarts) the dev server, which allows the user to view their Graphene app on localhost.
1310
- - `npm run graphene stop` - Stops the dev server.
1311
1324
 
1312
1325
  # AGENT INSTRUCTIONS
1313
1326
 
@@ -1318,6 +1331,6 @@ Follow these guidelines when working in a Graphene project.
1318
1331
  - Do not try to search the web for Graphene-specific info; you will not find anything. All the documentation is here in graphene.md.
1319
1332
  - When writing to a .gsql file, check your code with `npm run graphene check`.
1320
1333
  - When writing to a Graphene .md file:
1321
- - Always check your code with `npm run graphene check <mdPath>`.
1334
+ - Always check your code with `npm run graphene check <mdPath>`. Run the command with full permissions because the screenshot may not work in a sandbox.
1322
1335
  - Then do a visual check by either a) looking at the screenshot that `npm run graphene check <mdPath>` creates, or b) using your browser tool to open the .md file at `localhost:<port>/mdPath` (without the .md extension; default port 4000).
1323
- - Critique what you see: Are all the data values formatted in a way that is easy to read? Does the shape of the visualized data require an adjustment to scale, axis min/max, etc.? Are any visualizations missing data altogether? Is that visualization type really the best way to paint the picture? Etc.
1336
+ - Critique what you see: Are all the data values formatted in a way that is easy to read? Does the shape of the visualized data require an adjustment to scale, axis min/max, etc.? Are any visualizations missing data altogether? Is that visualization type really the best way to paint the picture? Etc.
@@ -23,3 +23,14 @@ export function serializeValue (value: unknown): string {
23
23
  let str = String(value)
24
24
  return `'${str.replace(/'/g, "''")}'`
25
25
  }
26
+
27
+ // Parse a comma-separated list into an array of trimmed strings.
28
+ // - Strings are split on commas; whitespace trimmed; empty entries removed.
29
+ // - Arrays are normalized by trimming string items and String()-ing non-strings.
30
+ // - null/undefined -> []
31
+ export function parseCommaList (value: unknown): string[] {
32
+ if (value === undefined || value === null) return []
33
+ if (Array.isArray(value)) return value.map(v => typeof v === 'string' ? v.trim() : String(v)).filter(v => v.length > 0)
34
+ if (typeof value === 'string') return value.split(',').map(v => v.trim()).filter(v => v.length > 0)
35
+ return [String(value).trim()].filter(v => v.length > 0)
36
+ }
@@ -10,6 +10,7 @@
10
10
  getFormatObjectFromString,
11
11
  } from '../component-utilities/formatting.js'
12
12
  import {getThemeStores} from '../component-utilities/themeStores'
13
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
13
14
 
14
15
  const {resolveColor} = getThemeStores()
15
16
  const props = getContext(propKey)
@@ -72,12 +73,14 @@
72
73
  $: xMismatch = $props.xMismatch
73
74
  $: columnSummary = $props.columnSummary
74
75
  $: series = seriesSet ? series : $props.series
75
- $: resolvedY = ySet ? y : $props.y
76
+ $: resolvedY = ySet ? parseCommaList(y) : $props.y
77
+ $: seriesOrder = parseCommaList(seriesOrder)
76
78
 
77
79
  $: {
78
- if (!series && typeof resolvedY !== 'object') {
80
+ if (!series && (!Array.isArray(resolvedY) || resolvedY.length === 1)) {
79
81
  stackName = undefined
80
- if (columnSummary?.[resolvedY]) name = name ?? formatTitle(resolvedY, columnSummary[resolvedY].title)
82
+ let col = Array.isArray(resolvedY) ? resolvedY[0] : resolvedY
83
+ if (columnSummary?.[col]) name = name ?? formatTitle(col, columnSummary[col].title)
81
84
  } else {
82
85
  stackName = 'area'
83
86
  data = getCompletedData(data, x, resolvedY, series, false, xType === 'value')
@@ -3,6 +3,7 @@
3
3
  import Area from './Area.svelte'
4
4
  import QueryLoad from './QueryLoad.svelte'
5
5
  import {getThemeStores} from '../component-utilities/themeStores'
6
+ import {parseCommaList} from '../component-utilities/inputUtils.ts'
6
7
 
7
8
  const {resolveColor, resolveColorsObject, resolveColorPalette} = getThemeStores()
8
9
 
@@ -83,7 +84,7 @@
83
84
  export let xLabelWrap = undefined
84
85
  </script>
85
86
 
86
- <QueryLoad data={data} fields={{x, y, series}} let:loaded>
87
+ <QueryLoad data={data} fields={{x, y: parseCommaList(y), series}} let:loaded>
87
88
  <Chart
88
89
  data={loaded}
89
90
  chartContext={{data, x, y, series}}