Skip to content

Commit 1342f4e

Browse files
authored
Merge pull request #9 from cube-js/add-tests
Add tests & add title and desc to dims
2 parents ffef93c + a95494b commit 1342f4e

File tree

15 files changed

+491
-73
lines changed

15 files changed

+491
-73
lines changed

.github/workflows/run_tests.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Run Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
# Checkout the repository
17+
- name: Checkout code
18+
uses: actions/checkout@v3
19+
20+
# Set up Python
21+
- name: Set up Python
22+
uses: actions/setup-python@v4
23+
with:
24+
python-version: "3.10" # Match the Python version specified in pyproject.toml
25+
26+
# Install PDM
27+
- name: Install PDM
28+
run: python -m pip install --upgrade pip pdm
29+
30+
# Install dependencies using PDM
31+
- name: Install dependencies
32+
run: pdm install --dev
33+
34+
# Run tests
35+
- name: Run tests
36+
run: pdm run pytest

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,9 @@ cython_debug/
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161161
.pdm-python
162-
162+
.pdm*
163163

164164
# default location to write files
165-
cubes/
166-
views/
167165
examples/
168166

169167

lkml2cube/parser/explores.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import re
22
import traceback
3-
import rich
43

54
from pprint import pformat
65

76
from lkml2cube.parser.views import parse_view
7+
from lkml2cube.parser.types import console
88

99
snake_case = r"\{([a-zA-Z]+(?:_[a-zA-Z]+)*\.[a-zA-Z]+(?:_[a-zA-Z]+)*)\}"
10-
console = rich.console.Console()
1110

1211

1312
def snakify(s):

lkml2cube/parser/loader.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
from os.path import abspath, dirname, join
77
from pathlib import Path
88

9+
from lkml2cube.parser.types import console
10+
911
visited_path = {}
10-
console = rich.console.Console()
1112

1213

1314
def update_namespace(namespace, new_file):

lkml2cube/parser/types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
import rich
2+
3+
4+
# console = rich.console.Console()
5+
class Console:
6+
def print(self, s, *args):
7+
print(s)
8+
9+
10+
console = Console()
11+
112
type_map = {
213
"zipcode": "string",
314
"string": "string",

lkml2cube/parser/views.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import copy
22
import traceback
3-
import rich
43

54
from pprint import pformat
6-
from lkml2cube.parser.types import type_map, literal_unicode
7-
8-
console = rich.console.Console()
5+
from lkml2cube.parser.types import type_map, literal_unicode, folded_unicode, console
96

107

118
def parse_view(lookml_model, raise_when_views_not_present=True):
129
cubes = []
1310
cube_def = {"cubes": cubes}
1411
rpl_table = lambda s: s.replace("${TABLE}", "{CUBE}").replace("${", "{")
12+
convert_to_literal = lambda s: (
13+
literal_unicode(rpl_table(s)) if "\n" in s else rpl_table(s)
14+
)
1515
sets = {}
1616

1717
if raise_when_views_not_present and "views" not in lookml_model:
@@ -102,10 +102,21 @@ def parse_view(lookml_model, raise_when_views_not_present=True):
102102

103103
cube_dimension = {
104104
"name": dimension["name"],
105-
"sql": rpl_table(dimension["sql"]),
105+
"sql": convert_to_literal(dimension["sql"]),
106106
"type": type_map[dimension["type"]],
107107
}
108108

109+
if "primary_key" in dimension:
110+
cube_dimension["primary_key"] = bool(
111+
dimension["primary_key"] == "yes"
112+
)
113+
114+
if "label" in dimension:
115+
cube_dimension["title"] = dimension["label"]
116+
117+
if "description" in dimension:
118+
cube_dimension["description"] = dimension["description"]
119+
109120
if "hidden" in dimension:
110121
cube_dimension["public"] = not bool(dimension["hidden"] == "yes")
111122

@@ -121,13 +132,17 @@ def parse_view(lookml_model, raise_when_views_not_present=True):
121132
)
122133
continue
123134
if len(bins) < 2:
135+
console.print(
136+
f'Dimension type: {dimension["type"]} requires more than 1 tiers',
137+
style="bold red",
138+
)
124139
pass
125140
else:
126141
tier_sql = f"CASE "
127142
for i in range(0, len(bins) - 1):
128143
tier_sql += f" WHEN {cube_dimension['sql']} >= {bins[i]} AND {cube_dimension['sql']} < {bins[i + 1]} THEN {bins[i]} "
129144
tier_sql += "ELSE NULL END"
130-
cube_dimension["sql"] = tier_sql
145+
cube_dimension["sql"] = folded_unicode(tier_sql)
131146
cube["dimensions"].append(cube_dimension)
132147

133148
for measure in view.get("measures", []):
@@ -145,7 +160,7 @@ def parse_view(lookml_model, raise_when_views_not_present=True):
145160
cube_measure["public"] = not bool(measure["hidden"] == "yes")
146161

147162
if measure["type"] != "count":
148-
cube_measure["sql"] = rpl_table(measure["sql"])
163+
cube_measure["sql"] = convert_to_literal(measure["sql"])
149164
elif "drill_fields" in measure:
150165
drill_members = []
151166
for drill_field in measure["drill_fields"]:

lkml2cube/tests/__init__.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
cubes:
2+
- description: Orders
3+
dimensions:
4+
- name: id
5+
primary_key: true
6+
sql: '{CUBE}."ID"'
7+
type: number
8+
- description: My description
9+
name: item_id
10+
sql: '{CUBE}.item_id'
11+
title: Item ID
12+
type: number
13+
- name: order_status
14+
sql: '{CUBE}."STATUS"'
15+
type: string
16+
- name: is_cancelled
17+
sql: case {CUBE}."STATUS" when "CANCELLED" then true else false end
18+
title: Is Cancelled
19+
type: boolean
20+
- name: created_at
21+
sql: '{CUBE}."CREATED_AT"'
22+
type: time
23+
- name: completed_at
24+
sql: '{CUBE}."COMPLETED_AT"'
25+
type: time
26+
joins: []
27+
measures:
28+
- name: count
29+
type: count
30+
- name: order_count_distinct
31+
sql: '{id}'
32+
type: count_distinct_approx
33+
name: orders
34+
sql_table: '{{_user_attributes[''ecom_database'']}}.{{_user_attributes[''ecom_schema'']}}."ORDERS"'
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
connection: "my_connection"
2+
3+
include: "/views/*.view.lkml" # include all views in the views/ folder in this project
4+
# include: "/**/*.view.lkml" # include all views in this project
5+
# include: "/**/*.dashboard.lookml" # include a LookML dashboard called my_dashboard
6+
7+
8+
explore: orders {
9+
label: "Orders Summary"
10+
11+
join: line_items {
12+
relationship: one_to_many
13+
sql_on: ${orders.id} = ${line_items.order_id} ;;
14+
type: left_outer
15+
}
16+
17+
join: products {
18+
relationship: many_to_one
19+
sql_on: ${line_items.product_id} = ${products.id} ;;
20+
type: left_outer
21+
}
22+
23+
}
24+
25+
explore: line_items {
26+
label: "Line Items Summary"
27+
28+
join: orders {
29+
relationship: many_to_one
30+
sql_on: ${line_items.order_id} = ${orders.id} ;;
31+
type: left_outer
32+
}
33+
34+
join: products {
35+
relationship: many_to_one
36+
sql_on: ${line_items.product_id} = ${products.id} ;;
37+
type: left_outer
38+
}
39+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# The name of this view in Looker is "Line Items"
2+
view: line_items {
3+
view_label: "Line Items"
4+
# The sql_table_name parameter indicates the underlying database table
5+
# to be used for all fields in this view.
6+
sql_table_name: {{_user_attributes['ecom_database']}}.{{_user_attributes['ecom_schema']}}."LINE_ITEMS"
7+
;;
8+
# In order to join this view in an Explore,
9+
# define primary_key: yes on a dimension that has no repeated values.
10+
11+
dimension: id {
12+
primary_key: yes
13+
type: number
14+
sql: ${TABLE}."ID" ;;
15+
}
16+
17+
# This table contains a foreign key to other tables.
18+
# Joins are defined in explores
19+
dimension: order_id {
20+
hidden: yes
21+
type: number
22+
sql: ${TABLE}."ORDER_ID" ;;
23+
}
24+
25+
dimension: product_id {
26+
hidden: yes
27+
type: number
28+
sql: ${TABLE}."PRODUCT_ID" ;;
29+
}
30+
31+
dimension: price {
32+
type: number
33+
sql: ${TABLE}."PRICE" ;;
34+
}
35+
36+
dimension: quantity {
37+
label: "Quantity"
38+
type: number
39+
sql: ${TABLE}."QUANTITY" ;;
40+
}
41+
42+
# You can reference other dimensions while defining a dimension.
43+
dimension: line_amount {
44+
type: number
45+
sql: ${quantity} * ${price};;
46+
}
47+
48+
dimension: quantity_bins {
49+
type: tier
50+
style: integer
51+
bins: [0,10,50,100]
52+
sql: ${quantity} ;;
53+
}
54+
55+
# A measure is a field that uses a SQL aggregate function. Here are defined sum and count
56+
# measures for this view, but you can also add measures of many different aggregates.
57+
58+
measure: total_quantity {
59+
type: sum
60+
sql: ${quantity} ;;
61+
}
62+
63+
measure: total_amount {
64+
type: sum
65+
sql: ${line_amount} ;;
66+
}
67+
68+
measure: count {
69+
type: count
70+
}
71+
72+
73+
# Dates and timestamps can be represented in Looker using a dimension group of type: time.
74+
# Looker converts dates and timestamps to the specified timeframes within the dimension group.
75+
76+
dimension_group: created_at {
77+
type: time
78+
timeframes: [
79+
raw,
80+
time,
81+
date,
82+
week,
83+
month,
84+
quarter,
85+
year
86+
]
87+
sql: ${TABLE}."CREATED_AT" ;;
88+
}
89+
90+
}

0 commit comments

Comments
 (0)