├── .gitattributes ├── LICENSE ├── LookML.sublime-syntax ├── README.md ├── lookml-sublime-syntax.png └── test_files ├── 01_order_items.view.lkml ├── 02_users.view.lkml ├── 03_inventory_items.view.lkml ├── 04_products.view.lkml ├── 05_distribution_centers.view.lkml ├── 11_order_facts.view.lkml ├── 12_user_order_facts.view.lkml ├── 13_repeat_purchase_facts.view.lkml ├── 22_affinity.view.lkml ├── 23_share_of_wallet.view.lkml ├── 24_inventory_snapshot.view.lkml ├── 25_trailing_sales_snapshot.view.lkml ├── 51_events.view.lkml ├── 52_sessions.view.lkml ├── 97_web_analytics_data_tool.view.lkml ├── 98_cohort_data_tool.view.lkml ├── 99_users_avatar.view.lkml └── thelook.model.lkml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jon Walls 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LookML.sublime-syntax: -------------------------------------------------------------------------------- 1 | %YAML 1.2 2 | --- 3 | # http://www.sublimetext.com/docs/3/syntax.html 4 | # https://www.sublimetext.com/docs/3/scope_naming.html#color_schemes 5 | 6 | name: LookML 7 | file_extensions: 8 | - lkml 9 | scope: source.looker 10 | 11 | contexts: 12 | 13 | # LOOKML 14 | main: 15 | - match: '#' 16 | scope: comment.looker 17 | push: 18 | - meta_scope: comment.looker 19 | - match: \n 20 | pop: true 21 | 22 | - include: keywords 23 | - include: numbers 24 | - include: variables 25 | - include: constants 26 | - include: double_quoted_strings 27 | - include: lists 28 | 29 | keywords: 30 | - match: '\b(explore|view|filter|parameter|dimension|dimension_group|measure|set)\b: ([A-Za-z0-9_-]+) ?({)' 31 | captures: 32 | 1: entity.name.looker 33 | 2: storage.type.looker 34 | 3: punctuation.definition.end.looker 35 | 36 | - match: '\b(sql_table_name|sql_on|sql_trigger|sql_latitude|sql_longitude|sql)\b(: ?)' 37 | captures: 38 | 1: variable.parameter.looker 39 | push: sql 40 | 41 | - match: '\b(html)\b(: ?)' 42 | captures: 43 | 1: variable.parameter.looker 44 | push: html 45 | 46 | - match: '\b(url|icon_url)\b(: ?)(")' 47 | captures: 48 | 1: variable.parameter.looker 49 | push: url 50 | 51 | - match: '\b([a-z0-9_]+)\b(: ?)([A-Za-z0-9_\-]+ ?)' 52 | captures: 53 | 1: variable.parameter.looker 54 | 3: storage.type.looker 55 | 56 | - match: '\b(?i)([a-z0-9_]+)\b(: ?)' 57 | captures: 58 | 1: variable.parameter.looker 59 | 60 | - match: '\b(string|unquoted|number|date|time|datetime|yesno)\b' 61 | scope: storage.type.looker 62 | 63 | - match: '\b(count|count_distinct|sum|average|median)\b' 64 | scope: storage.type.looker 65 | 66 | - match: '\b(gbp|gbp_0|usd|usd_0|percent_0|percent_1|percent_2)\b' 67 | scope: storage.type.looker 68 | 69 | - match: (}) 70 | scope: punctuation.definition.end.looker 71 | 72 | double_quoted_strings: 73 | - match: '"' 74 | scope: punctuation.definition.string.begin.looker 75 | push: 76 | - meta_scope: string.quoted.double.looker 77 | - include: templates 78 | - match: '"' 79 | scope: punctuation.definition.string.end.looker 80 | pop: true 81 | 82 | single_quoted_strings: 83 | - match: "'" 84 | scope: punctuation.definition.string.begin.looker 85 | push: 86 | - meta_scope: string.quoted.single.looker 87 | - include: templates 88 | - match: "'" 89 | scope: punctuation.definition.string.end.looker 90 | pop: true 91 | 92 | variables: 93 | - match: '\$\{' 94 | scope: punctuation.definition.variable.begin.looker 95 | push: 96 | - meta_scope: storage.type.field_reference.looker 97 | - match: '\}' 98 | scope: punctuation.definition.variable.end.looker 99 | pop: true 100 | 101 | constants: 102 | - match: '\b(yes|no)\b' 103 | scope: constant.language.looker 104 | 105 | numbers: 106 | - match: '\b([0-9/.]+)' 107 | scope: constant.numeric.looker 108 | 109 | templates: 110 | - match: '\{\%' 111 | scope: punctuation.separator.looker 112 | push: liquid_control 113 | 114 | - match: '\{\{' 115 | scope: punctuation.separator.liquid.looker 116 | push: liquid_variable 117 | 118 | # LISTS 119 | lists: 120 | - match: '\[' 121 | scope: punctuation.definition.begin.looker 122 | push: 123 | - meta_scope: string.list.looker 124 | - match: '#' 125 | scope: comment.looker 126 | push: 127 | - meta_scope: comment.looker 128 | - match: \n 129 | pop: true 130 | - match: '[a-zA-Z0-9_-]' 131 | scope: storage.type.list.looker 132 | - match: '\]' 133 | scope: punctuation.definition.end.looker 134 | pop: true 135 | 136 | # SQL 137 | sql: 138 | - clear_scopes: true 139 | - meta_scope: string.sql.looker 140 | - include: variables 141 | - include: numbers 142 | - include: double_quoted_strings 143 | - include: single_quoted_strings 144 | - include: templates 145 | 146 | - match: '\-\-' 147 | scope: comment.sql.looker 148 | push: 149 | - meta_scope: comment.sql.looker 150 | - match: '\n' 151 | pop: true 152 | 153 | - match: (\-|\+|\*|\/|%\/%|%%|\^|\|\|) 154 | scope: keyword.operator.arithmetic.looker 155 | 156 | - match: '\b(=|!=|<>|<|>|<=|>=|AND|OR|IS|NOT)\b' 157 | scope: keyword.operator.comparison.looker 158 | 159 | - match: '(?i)\b(SELECT|AS|FROM|WHERE|GROUP BY|ORDER BY|HAVING|LEFT|JOIN|ON)\b' 160 | scope: keyword.control.sql.looker 161 | 162 | - match: '(?i)\b(CASE|WHEN|IN|THEN|ELSE|END)\b' 163 | scope: keyword.control.sql.looker 164 | 165 | - match: '(?i)\b(ABS|ACOS|ASCII|ASIN|ATAN|ATAN2|AVG|CEILING|COALESCE|COS|COT|COUNT|COUNTD|DEGREES|DIV|EXP|FLOAT|FLOOR|HEXBINX|HEXBINY|INT|LN|LOG|MAX|MIN|NOW|NULLIF|OVER|PARTITION BY|POWER|RADIANS|ROUND|RUNNING_AVG|RUNNING_COUNT|RUNNING_MAX|RUNNING_MIN|RUNNING_SUM|SIGN|SIN|SQRT|SQUARE|STDEV|STDEVP|SUM|TAN|TOTAL|VAR|VARP|WINDOW_AVG|WINDOW_COUNT|WINDOW_MAX|WINDOW_MEDIAN|WINDOW_MIN|WINDOW_PERCENTILE|WINDOW_STDEV|WINDOW_STDEVP|WINDOW_SUM|WINDOW_VAR|WINDOW_VARP|YEAR|ATTR|CHAR|CONTAINS|ENDSWITH|EXCLUDE|FIND|FIRST|FIXED|FULLNAME|IFNULL|INCLUDE|INDEX|ISDATE|ISFULLNAME|ISMEMBEROF|ISNULL|ISUSERNAME|LAST|LEFT|LEN|LOOKUP|LOWER|LTRIM|MID|PREVIOUS_VALUE|RANK|RANK_DENSE|RANK_MODIFIED_RANK_PERCENTILE|RANK_UNIQUE|RAWSQLAGG_BOOL|RAWSQLAGG_DATE|RAWSQLAGG_DATETIME|RAWSQLAGG_INT|RAWSQLAGG_REAL|RAWSQLAGG_STR|RAWSQL_BOOL|RAWSQL_DATE|RAWSQL_DATETIME|RAWSQL_INT|RAWSQL_REAL|RAWSQL_STR|REPLACE|RIGHT|RTRIM|SCRIPT_BOOL|SCRIPT_INT|SCRIPT_REAL|SCRIPT_STR|SIZE|SPACE|SPLIT|STARTSWITH|STR|TODAY|TRIM|UPPER|USERDOMAIN|USERNAME|ZN)\b' 166 | scope: variable.function.sql.looker 167 | 168 | - match: '\b(?i)(TRUE|FALSE|NULL|PI)\b' 169 | scope: constant.language.sql.looker 170 | 171 | - match: '(?i)\b(CURRENT\_?DATE|GET\_?DATE|DATE\_?ADD|DATE\_?DIFF|DATE\_?NAME|DATE\_?PART|DATETIME|DATE\_?TRUNC|EXTRACT|FROM|TIMESTAMP|DATE_FROM_PARTS)\b' 172 | scope: variable.function.sql.looker 173 | 174 | - match: '\b(?i)(hour|day|dayofweek|dayofyear|week|month|year)\b' 175 | scope: constant.language.sql.looker 176 | 177 | - match: ';;' 178 | scope: comment.endofstatement.looker 179 | pop: true 180 | 181 | # HTML 182 | html: 183 | - clear_scopes: true 184 | - meta_scope: text.html.basic.looker 185 | - include: templates 186 | 187 | - match: '<(/?)([a-zA-Z]+)' 188 | captures: 189 | 1: punctuation.definition.tag.begin.html.looker 190 | 2: entity.name.tag.html.looker 191 | push: html_tag 192 | 193 | - match: ';;' 194 | scope: comment.endofstatement.hmtl.looker 195 | pop: true 196 | 197 | html_tag: 198 | - meta_scope: text.html.looker 199 | - include: numbers 200 | 201 | - match: '(src|href)(=\")' 202 | captures: 203 | 1: entity.name.tag.attribue.looker 204 | push: url 205 | 206 | - match: '[a-zA-Z]+' 207 | scope: entity.name.tag.attribute.looker 208 | 209 | - match: '\=\"' 210 | scope: punctuation.html.tag.looker 211 | push: html_tag_attribute_string 212 | 213 | - match: '\>' 214 | scope: punctuation.definition.tag.end.html.looker 215 | pop: true 216 | 217 | html_tag_attribute_string: 218 | - clear_scopes: true 219 | - include: numbers 220 | - include: templates 221 | - meta_scope: string.html.tag.attribute.value.looker 222 | 223 | - match: '([a-zA-Z\-]+):' 224 | captures: 225 | 1: meta.attribute-with-value.html.looker 226 | 227 | - match: '([a-zA-Z\-]+)' 228 | scope: constant.language.html.tag.attribute.value.looker 229 | 230 | - match: '"' 231 | scope: punctuation.html.tag.looker 232 | pop: true 233 | 234 | # URL 235 | url: 236 | - clear_scopes: true 237 | - meta_scope: string.url.looker 238 | - include: templates 239 | 240 | - match: (https?:\/\/) 241 | captures: 242 | 1: entity.name.tag.html.looker 243 | 244 | - match: '(\?|\&)([a-zA-Z0-9_]+)(\=)' 245 | captures: 246 | 2: storage.type.url_param.looker 247 | 248 | - match: '"' 249 | pop: true 250 | 251 | # LIQUID TEMPLATES 252 | liquid_control: 253 | - clear_scopes: true 254 | - include: numbers 255 | - meta_scope: variable.function.liquid.control.looker 256 | 257 | - match: \bassign|parameter\b 258 | scope: keyword.liquid.looker 259 | 260 | - match: \b(if|elsif|else|endif|unless|endunless|case|when|endcase|condition|endcondition)\b 261 | scope: keyword.control.liquid.looker 262 | 263 | - match: (==|!=|<>|<|>|<=|>=|or|and|contains) 264 | scope: keyword.operator.comparison.looker 265 | 266 | - match: \b([a-zA-Z0-9_]+)\b 267 | scope: storage.type.liquid.looker 268 | 269 | - include: double_quoted_strings 270 | - include: single_quoted_strings 271 | 272 | - match: '\%\}' 273 | scope: punctuation.terminator.liquid.looker 274 | pop: true 275 | 276 | liquid_variable: 277 | - clear_scopes: true 278 | - meta_scope: variable.function.liquid.variable.looker 279 | - match: '\}\}' 280 | scope: punctuation.terminator.liquid.looker 281 | pop: true 282 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lookml-sublime-syntax – Looker Syntax Highlighting for Sublime Text 3 2 | 3 | This file can be added to your Sublime Text 3 configuration to display LookML files with syntax highlighting. This applies to both LookML and embedded Liquid, HTML, URLs and SQL code. 4 | 5 | ## Status and Support 6 | 7 | The sublime-syntax format for syntax definitions is supported by Sublime Text 3 only; prior versions will not work. 8 | 9 | This syntax definition is NOT supported or warranteed by Looker in any way. Please do not contact Looker for support. Issues can be logged via https://github.com/ContrastingSounds/lookml-sublime-syntax/issues 10 | 11 | ## Installation 12 | 13 | 1. Download the file, either by cloning the git repository (which includes a directory of test LookML files), or saving this link directly to your computer: https://raw.githubusercontent.com/ContrastingSounds/lookml-sublime-syntax/master/LookML.sublime-syntax 14 | 2. On Mac: 15 | - Go to Sublime Text -> Preferences -> Browse Packages 16 | - This will open up Sublime's Packages folder in a Finder window 17 | - Drop the 'LookML.sublime-syntax' file in to the folder 18 | 3. On Windows: 19 | - Go to Preferences -> Browse Packages 20 | - This will open up Sublime's Packages folder in a File Explorer window 21 | - Drop the 'LookML.sublime-syntax' file in to the folder 22 | 23 | ## Usage 24 | 25 | Once installed, Sublime Text 3 will now display any .lkml file with full highlighting for LookML syntax: 26 | 27 | ![screencap](lookml-sublime-syntax.png) 28 | -------------------------------------------------------------------------------- /lookml-sublime-syntax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ContrastingSounds/lookml-sublime-syntax/e8328d20f882a6f5db591d0957d7eb0d1fe460e5/lookml-sublime-syntax.png -------------------------------------------------------------------------------- /test_files/01_order_items.view.lkml: -------------------------------------------------------------------------------- 1 | view: order_items { 2 | sql_table_name: order_items ;; 3 | ########## IDs, Foreign Keys, Counts ########### 4 | 5 | dimension: id { 6 | primary_key: yes 7 | type: number 8 | sql: ${TABLE}.id ;; 9 | } 10 | 11 | dimension: inventory_item_id { 12 | type: number 13 | hidden: yes 14 | sql: ${TABLE}.inventory_item_id ;; 15 | } 16 | 17 | dimension: user_id { 18 | type: number 19 | hidden: yes 20 | sql: ${TABLE}.user_id ;; 21 | } 22 | 23 | measure: count { 24 | type: count_distinct 25 | sql: ${id} ;; 26 | drill_fields: [detail*] 27 | } 28 | 29 | measure: order_count { 30 | view_label: "Orders" 31 | type: count_distinct 32 | drill_fields: [detail*] 33 | sql: ${order_id} ;; 34 | } 35 | 36 | 37 | measure: count_last_28d { 38 | label: "Count Sold in Trailing 28 Days" 39 | type: count_distinct 40 | sql: ${id} ;; 41 | hidden: yes 42 | filters: 43 | {field:created_date 44 | value: "28 days" 45 | }} 46 | 47 | dimension: order_id { 48 | type: number 49 | sql: ${TABLE}.order_id ;; 50 | 51 | 52 | action: { 53 | label: "Send this to slack channel" 54 | url: "https://hooks.zapier.com/hooks/catch/1662138/tvc3zj/" 55 | 56 | param: { 57 | name: "user_dash_link" 58 | value: "https://demo.looker.com/dashboards/160?Email={{ users.email._value}}" 59 | } 60 | 61 | form_param: { 62 | name: "Message" 63 | type: textarea 64 | default: "Hey, 65 | Could you check out order #{{value}}. It's saying its {{status._value}}, 66 | but the customer is reaching out to us about it. 67 | ~{{ _user_attributes.first_name}}" 68 | } 69 | 70 | form_param: { 71 | name: "Recipient" 72 | type: select 73 | default: "zevl" 74 | option: { 75 | name: "zevl" 76 | label: "Zev" 77 | } 78 | option: { 79 | name: "slackdemo" 80 | label: "Slack Demo User" 81 | } 82 | 83 | } 84 | 85 | form_param: { 86 | name: "Channel" 87 | type: select 88 | default: "cs" 89 | option: { 90 | name: "cs" 91 | label: "Customer Support" 92 | } 93 | option: { 94 | name: "general" 95 | label: "General" 96 | } 97 | 98 | } 99 | 100 | 101 | } 102 | 103 | 104 | 105 | } 106 | 107 | ########## Time Dimensions ########## 108 | 109 | dimension_group: returned { 110 | type: time 111 | timeframes: [time, date, week, month, raw] 112 | sql: ${TABLE}.returned_at ;; 113 | } 114 | 115 | dimension_group: shipped { 116 | type: time 117 | timeframes: [date, week, month, raw] 118 | sql: ${TABLE}.shipped_at ;; 119 | } 120 | 121 | dimension_group: delivered { 122 | type: time 123 | timeframes: [date, week, month, raw] 124 | sql: ${TABLE}.delivered_at ;; 125 | } 126 | 127 | dimension_group: created { 128 | #X# group_label:"Order Date" 129 | type: time 130 | timeframes: [time, hour, date, week, month, year, hour_of_day, day_of_week, month_num, raw, week_of_year] 131 | sql: ${TABLE}.created_at ;; 132 | } 133 | 134 | dimension: reporting_period { 135 | group_label: "Order Date" 136 | sql: CASE 137 | WHEN date_part('year',${created_raw}) = date_part('year',current_date) 138 | AND ${created_raw} < CURRENT_DATE 139 | THEN 'This Year to Date' 140 | 141 | WHEN date_part('year',${created_raw}) + 1 = date_part('year',current_date) 142 | AND date_part('dayofyear',${created_raw}) <= date_part('dayofyear',current_date) 143 | THEN 'Last Year to Date' 144 | 145 | END 146 | ;; 147 | } 148 | 149 | dimension: days_since_sold { 150 | hidden: yes 151 | sql: datediff('day',${created_raw},CURRENT_DATE) ;; 152 | } 153 | 154 | dimension: months_since_signup { 155 | view_label: "Orders" 156 | type: number 157 | sql: DATEDIFF('month',${users.created_raw},${created_raw}) ;; 158 | } 159 | 160 | ########## Logistics ########## 161 | 162 | dimension: status { 163 | sql: ${TABLE}.status ;; 164 | } 165 | 166 | dimension: days_to_process { 167 | type: number 168 | sql: CASE 169 | WHEN ${status} = 'Processing' THEN DATEDIFF('day',${created_raw},GETDATE())*1.0 170 | WHEN ${status} IN ('Shipped', 'Complete', 'Returned') THEN DATEDIFF('day',${created_raw},${shipped_raw})*1.0 171 | WHEN ${status} = 'Cancelled' THEN NULL 172 | END 173 | ;; 174 | } 175 | 176 | dimension: shipping_time { 177 | type: number 178 | sql: datediff('day',${shipped_raw},${delivered_raw})*1.0 ;; 179 | } 180 | 181 | measure: average_days_to_process { 182 | type: average 183 | value_format_name: decimal_2 184 | sql: ${days_to_process} ;; 185 | } 186 | 187 | measure: average_shipping_time { 188 | type: average 189 | value_format_name: decimal_2 190 | sql: ${shipping_time} ;; 191 | } 192 | 193 | ########## Financial Information ########## 194 | 195 | dimension: sale_price { 196 | type: number 197 | value_format_name: usd 198 | sql: ${TABLE}.sale_price ;; 199 | } 200 | 201 | dimension: gross_margin { 202 | type: number 203 | value_format_name: usd 204 | sql: ${sale_price} - ${inventory_items.cost} ;; 205 | } 206 | 207 | dimension: item_gross_margin_percentage { 208 | type: number 209 | value_format_name: percent_2 210 | sql: 1.0 * ${gross_margin}/NULLIF(${sale_price},0) ;; 211 | } 212 | 213 | dimension: item_gross_margin_percentage_tier { 214 | type: tier 215 | sql: 100*${item_gross_margin_percentage} ;; 216 | tiers: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 217 | style: interval 218 | } 219 | 220 | measure: total_sale_price { 221 | type: sum 222 | value_format_name: usd 223 | sql: ${sale_price} ;; 224 | drill_fields: [detail*] 225 | } 226 | 227 | measure: total_gross_margin { 228 | type: sum 229 | value_format_name: usd 230 | sql: ${gross_margin} ;; 231 | drill_fields: [detail*] 232 | } 233 | 234 | measure: average_sale_price { 235 | type: average 236 | value_format_name: usd 237 | sql: ${sale_price} ;; 238 | drill_fields: [detail*] 239 | } 240 | 241 | measure: median_sale_price { 242 | type: median 243 | value_format_name: usd 244 | sql: ${sale_price} ;; 245 | drill_fields: [detail*] 246 | } 247 | 248 | measure: average_gross_margin { 249 | type: average 250 | value_format_name: usd 251 | sql: ${gross_margin} ;; 252 | drill_fields: [detail*] 253 | } 254 | 255 | measure: total_gross_margin_percentage { 256 | type: number 257 | value_format_name: percent_2 258 | sql: 1.0 * ${total_gross_margin}/ NULLIF(${total_sale_price},0) ;; 259 | } 260 | 261 | measure: average_spend_per_user { 262 | type: number 263 | value_format_name: usd 264 | sql: 1.0 * ${total_sale_price} / NULLIF(${users.count},0) ;; 265 | drill_fields: [detail*] 266 | } 267 | 268 | ########## Return Information ########## 269 | 270 | dimension: is_returned { 271 | type: yesno 272 | sql: ${returned_raw} IS NOT NULL ;; 273 | } 274 | 275 | measure: returned_count { 276 | type: count_distinct 277 | sql: ${id} ;; 278 | filters: { 279 | field: is_returned 280 | value: "yes" 281 | } 282 | drill_fields: [detail*] 283 | } 284 | 285 | measure: returned_total_sale_price { 286 | type: sum 287 | value_format_name: usd 288 | sql: ${sale_price} ;; 289 | filters: { 290 | field: is_returned 291 | value: "yes" 292 | } 293 | } 294 | 295 | measure: return_rate { 296 | type: number 297 | value_format_name: percent_2 298 | sql: 1.0 * ${returned_count} / nullif(${count},0) ;; 299 | } 300 | 301 | 302 | ########## Repeat Purchase Facts ########## 303 | 304 | dimension: days_until_next_order { 305 | type: number 306 | view_label: "Repeat Purchase Facts" 307 | sql: DATEDIFF('day',${created_raw},${repeat_purchase_facts.next_order_raw}) ;; 308 | } 309 | 310 | dimension: repeat_orders_within_30d { 311 | type: yesno 312 | view_label: "Repeat Purchase Facts" 313 | sql: ${days_until_next_order} <= 30 ;; 314 | } 315 | 316 | measure: count_with_repeat_purchase_within_30d { 317 | type: count_distinct 318 | sql: ${id} ;; 319 | view_label: "Repeat Purchase Facts" 320 | 321 | filters: { 322 | field: repeat_orders_within_30d 323 | value: "Yes" 324 | } 325 | } 326 | 327 | measure: 30_day_repeat_purchase_rate { 328 | description: "The percentage of customers who purchase again within 30 days" 329 | view_label: "Repeat Purchase Facts" 330 | type: number 331 | value_format_name: percent_1 332 | sql: 1.0 * ${count_with_repeat_purchase_within_30d} / NULLIF(${count},0) ;; 333 | drill_fields: [products.brand, order_count, count_with_repeat_purchase_within_30d] 334 | } 335 | 336 | measure: first_purchase_count { 337 | view_label: "Orders" 338 | type: count_distinct 339 | sql: ${order_id} ;; 340 | 341 | filters: { 342 | field: order_facts.is_first_purchase 343 | value: "Yes" 344 | } 345 | # customized drill path for first_purchase_count 346 | drill_fields: [user_id, order_id, created_date, users.traffic_source] 347 | link: { 348 | label: "New User's Behavior by Traffic Source" 349 | url: " 350 | {% assign vis_config = '{ 351 | \"type\": \"looker_column\", 352 | \"show_value_labels\": true, 353 | \"y_axis_gridlines\": true, 354 | \"show_view_names\": false, 355 | \"y_axis_combined\": false, 356 | \"show_y_axis_labels\": true, 357 | \"show_y_axis_ticks\": true, 358 | \"show_x_axis_label\": false, 359 | \"value_labels\": \"legend\", 360 | \"label_type\": \"labPer\", 361 | \"font_size\": \"13\", 362 | \"colors\": [ 363 | \"#1ea8df\", 364 | \"#a2dcf3\", 365 | \"#929292\" 366 | ], 367 | \"hide_legend\": false, 368 | \"y_axis_orientation\": [ 369 | \"left\", 370 | \"right\" 371 | ], 372 | \"y_axis_labels\": [ 373 | \"Average Sale Price ($)\" 374 | ] 375 | }' %} 376 | {{ hidden_first_purchase_visualization_link._link }}&vis_config={{ vis_config | encode_uri }}&sorts=users.average_lifetime_orders+descc&toggle=dat,pik,vis&limit=5000" 377 | } 378 | } 379 | 380 | ########## Dynamic Sales Cohort App ########## 381 | 382 | filter: cohort_by { 383 | type: string 384 | hidden: yes 385 | suggestions: ["Week", "Month", "Quarter", "Year"] 386 | } 387 | 388 | filter: metric { 389 | type: string 390 | hidden: yes 391 | suggestions: ["Order Count", "Gross Margin", "Total Sales", "Unique Users"] 392 | } 393 | 394 | dimension_group: first_order_period { 395 | type: time 396 | timeframes: [date] 397 | hidden: yes 398 | sql: CAST(DATE_TRUNC({% parameter cohort_by %}, ${user_order_facts.first_order_date}) AS DATE) 399 | ;; 400 | } 401 | 402 | dimension: periods_as_customer { 403 | type: number 404 | hidden: yes 405 | sql: DATEDIFF({% parameter cohort_by %}, ${user_order_facts.first_order_date}, ${user_order_facts.latest_order_date}) 406 | ;; 407 | } 408 | 409 | measure: cohort_values_0 { 410 | type: count_distinct 411 | hidden: yes 412 | sql: CASE WHEN {% parameter metric %} = 'Order Count' THEN ${id} 413 | WHEN {% parameter metric %} = 'Unique Users' THEN ${users.id} 414 | ELSE null 415 | END 416 | ;; 417 | } 418 | 419 | measure: cohort_values_1 { 420 | type: sum 421 | hidden: yes 422 | sql: CASE WHEN {% parameter metric %} = 'Gross Margin' THEN ${gross_margin} 423 | WHEN {% parameter metric %} = 'Total Sales' THEN ${sale_price} 424 | ELSE 0 425 | END 426 | ;; 427 | } 428 | 429 | measure: values { 430 | type: number 431 | hidden: yes 432 | sql: ${cohort_values_0} + ${cohort_values_1} ;; 433 | } 434 | 435 | measure: hidden_first_purchase_visualization_link { 436 | hidden: yes 437 | view_label: "Orders" 438 | type: count_distinct 439 | sql: ${order_id} ;; 440 | 441 | filters: { 442 | field: order_facts.is_first_purchase 443 | value: "Yes" 444 | } 445 | drill_fields: [users.traffic_source, user_order_facts.average_lifetime_revenue, user_order_facts.average_lifetime_orders] 446 | } 447 | 448 | 449 | 450 | 451 | ########## Sets ########## 452 | 453 | set: detail { 454 | fields: [id, order_id, status, created_date, sale_price, products.brand, products.item_name, users.portrait, users.name, users.email] 455 | } 456 | set: return_detail { 457 | fields: [id, order_id, status, created_date, returned_date, sale_price, products.brand, products.item_name, users.portrait, users.name, users.email] 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /test_files/02_users.view.lkml: -------------------------------------------------------------------------------- 1 | view: users { 2 | sql_table_name: users ;; 3 | ## Demographics ## 4 | 5 | dimension: id { 6 | primary_key: yes 7 | type: number 8 | sql: ${TABLE}.id ;; 9 | tags: ["user_id"] 10 | } 11 | 12 | dimension: first_name { 13 | hidden: yes 14 | sql: INITCAP(${TABLE}.first_name) ;; 15 | } 16 | 17 | dimension: last_name { 18 | hidden: yes 19 | sql: INITCAP(${TABLE}.last_name) ;; 20 | } 21 | 22 | dimension: name { 23 | sql: ${first_name} || ' ' || ${last_name} ;; 24 | } 25 | 26 | dimension: age { 27 | type: number 28 | sql: ${TABLE}.age ;; 29 | } 30 | 31 | dimension: age_tier { 32 | type: tier 33 | tiers: [0, 10, 20, 30, 40, 50, 60, 70] 34 | style: integer 35 | sql: ${age} ;; 36 | } 37 | 38 | dimension: gender { 39 | sql: ${TABLE}.gender ;; 40 | } 41 | 42 | dimension: gender_short { 43 | sql: LOWER(LEFT(${gender},1)) ;; 44 | } 45 | 46 | dimension: user_image { 47 | sql: ${image_file} ;; 48 | html: ;; 49 | } 50 | 51 | dimension: email { 52 | sql: ${TABLE}.email ;; 53 | tags: ["email"] 54 | 55 | link: { 56 | label: "User Lookup Dashboard" 57 | url: "http://demo.looker.com/dashboards/160?Email={{ value | encode_uri }}" 58 | icon_url: "http://www.looker.com/favicon.ico" 59 | } 60 | action: { 61 | label: "Email Promotion to Customer" 62 | url: "https://desolate-refuge-53336.herokuapp.com/posts" 63 | icon_url: "https://sendgrid.com/favicon.ico" 64 | param: { 65 | name: "some_auth_code" 66 | value: "abc123456" 67 | } 68 | form_param: { 69 | name: "Subject" 70 | required: yes 71 | default: "Thank you {{ users.name._value }}" 72 | } 73 | form_param: { 74 | name: "Body" 75 | type: textarea 76 | required: yes 77 | default: 78 | "Dear {{ users.first_name._value }}, 79 | 80 | Thanks for your loyalty to the Look. We'd like to offer you a 10% discount 81 | on your next purchase! Just use the code LOYAL when checking out! 82 | 83 | Your friends at the Look" 84 | } 85 | } 86 | required_fields: [name, first_name] 87 | } 88 | 89 | dimension: image_file { 90 | hidden: yes 91 | sql: ('https://docs.looker.com/assets/images/'||${gender_short}||'.jpg') ;; 92 | } 93 | 94 | ## Demographics ## 95 | 96 | dimension: city { 97 | sql: ${TABLE}.city ;; 98 | drill_fields: [zip] 99 | } 100 | 101 | dimension: state { 102 | sql: ${TABLE}.state ;; 103 | map_layer_name: us_states 104 | drill_fields: [zip, city] 105 | } 106 | 107 | dimension: zip { 108 | type: zipcode 109 | sql: ${TABLE}.zip ;; 110 | } 111 | 112 | dimension: uk_postcode { 113 | label: "UK Postcode" 114 | sql: CASE WHEN ${TABLE}.country = 'UK' THEN TRANSLATE(LEFT(${zip},2),'0123456789','') END ;; 115 | map_layer_name: uk_postcode_areas 116 | drill_fields: [city, zip] 117 | } 118 | 119 | dimension: country { 120 | map_layer_name: countries 121 | drill_fields: [state, city] 122 | sql: CASE WHEN ${TABLE}.country = 'UK' THEN 'United Kingdom' 123 | ELSE ${TABLE}.country 124 | END 125 | ;; 126 | } 127 | 128 | dimension: location { 129 | type: location 130 | sql_latitude: ${TABLE}.latitude ;; 131 | sql_longitude: ${TABLE}.longitude ;; 132 | } 133 | 134 | dimension: approx_location { 135 | type: location 136 | drill_fields: [location] 137 | sql_latitude: round(${TABLE}.latitude,1) ;; 138 | sql_longitude: round(${TABLE}.longitude,1) ;; 139 | } 140 | 141 | ## Other User Information ## 142 | 143 | dimension_group: created { 144 | type: time 145 | # timeframes: [time, date, week, month, raw] 146 | sql: ${TABLE}.created_at ;; 147 | } 148 | 149 | dimension: history { 150 | sql: ${TABLE}.id ;; 151 | html: Order History 152 | ;; 153 | } 154 | 155 | dimension: traffic_source { 156 | sql: ${TABLE}.traffic_source ;; 157 | } 158 | 159 | dimension: ssn { 160 | # dummy field used in next dim 161 | hidden: yes 162 | type: number 163 | sql: lpad(cast(round(random() * 10000, 0) as char(4)), 4, '0') ;; 164 | } 165 | 166 | dimension: ssn_last_4 { 167 | label: "SSN Last 4" 168 | description: "Only users with sufficient permissions will see this data" 169 | type: string 170 | sql: 171 | CASE WHEN '{{_user_attributes["can_see_sensitive_data"]}}' = 'yes' 172 | THEN ${ssn} 173 | ELSE MD5(${ssn}||'salt') 174 | END;; 175 | html: 176 | {% if _user_attributes["can_see_sensitive_data"] == 'yes' %} 177 | {{ value }} 178 | {% else %} 179 | #### 180 | {% endif %} ;; 181 | } 182 | 183 | ## MEASURES ## 184 | 185 | measure: count { 186 | type: count 187 | drill_fields: [detail*] 188 | } 189 | 190 | measure: count_percent_of_total { 191 | label: "Count (Percent of Total)" 192 | type: percent_of_total 193 | sql: ${count} ;; 194 | drill_fields: [detail*] 195 | } 196 | 197 | measure: average_age { 198 | type: average 199 | value_format_name: decimal_2 200 | sql: ${age} ;; 201 | drill_fields: [detail*] 202 | } 203 | 204 | set: detail { 205 | fields: [id, name, email, age, created_date, orders.count, order_items.count] 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /test_files/03_inventory_items.view.lkml: -------------------------------------------------------------------------------- 1 | view: inventory_items { 2 | sql_table_name: inventory_items ;; 3 | ## DIMENSIONS ## 4 | 5 | dimension: id { 6 | primary_key: yes 7 | type: number 8 | sql: ${TABLE}.id ;; 9 | } 10 | 11 | dimension: cost { 12 | type: number 13 | value_format_name: usd 14 | sql: ${TABLE}.cost ;; 15 | } 16 | 17 | dimension_group: created { 18 | type: time 19 | timeframes: [time, date, week, month, raw] 20 | sql: ${TABLE}.created_at ;; 21 | } 22 | 23 | dimension: product_id { 24 | type: number 25 | hidden: yes 26 | sql: ${TABLE}.product_id ;; 27 | } 28 | 29 | dimension_group: sold { 30 | type: time 31 | timeframes: [time, date, week, month, raw] 32 | sql: ${TABLE}.sold_at ;; 33 | } 34 | 35 | dimension: is_sold { 36 | type: yesno 37 | sql: ${sold_raw} is not null ;; 38 | } 39 | 40 | dimension: days_in_inventory { 41 | description: "days between created and sold date" 42 | type: number 43 | sql: DATEDIFF('day', ${created_raw}, coalesce(${sold_raw},CURRENT_DATE)) ;; 44 | } 45 | 46 | dimension: days_in_inventory_tier { 47 | type: tier 48 | sql: ${days_in_inventory} ;; 49 | style: integer 50 | tiers: [0, 5, 10, 20, 40, 80, 160, 360] 51 | } 52 | 53 | dimension: days_since_arrival { 54 | description: "days since created - useful when filtering on sold yesno for items still in inventory" 55 | type: number 56 | sql: DATEDIFF('day', ${created_date}, GETDATE()) ;; 57 | } 58 | 59 | dimension: days_since_arrival_tier { 60 | type: tier 61 | sql: ${days_since_arrival} ;; 62 | style: integer 63 | tiers: [0, 5, 10, 20, 40, 80, 160, 360] 64 | } 65 | 66 | dimension: product_distribution_center_id { 67 | hidden: yes 68 | sql: ${TABLE}.product_distribution_center_id ;; 69 | } 70 | 71 | ## MEASURES ## 72 | 73 | measure: sold_count { 74 | type: count 75 | drill_fields: [detail*] 76 | 77 | filters: { 78 | field: is_sold 79 | value: "Yes" 80 | } 81 | } 82 | 83 | measure: sold_percent { 84 | type: number 85 | value_format_name: percent_2 86 | sql: 1.0 * ${sold_count}/NULLIF(${count},0) ;; 87 | } 88 | 89 | measure: total_cost { 90 | type: sum 91 | value_format_name: usd 92 | sql: ${cost} ;; 93 | } 94 | 95 | measure: average_cost { 96 | type: average 97 | value_format_name: usd 98 | sql: ${cost} ;; 99 | } 100 | 101 | measure: count { 102 | type: count 103 | drill_fields: [detail*] 104 | } 105 | 106 | measure: number_on_hand { 107 | type: count 108 | drill_fields: [detail*] 109 | 110 | filters: { 111 | field: is_sold 112 | value: "No" 113 | } 114 | } 115 | 116 | measure: stock_coverage_ratio { 117 | type: number 118 | description: "Stock on Hand vs Trailing 28d Sales Ratio" 119 | sql: 1.0 * ${number_on_hand} / nullif(${order_items.count_last_28d},0) ;; 120 | value_format_name: decimal_2 121 | html:

{{ rendered_value }}

;; 122 | } 123 | 124 | set: detail { 125 | fields: [id, products.item_name, products.category, products.brand, products.department, cost, created_time, sold_time] 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /test_files/04_products.view.lkml: -------------------------------------------------------------------------------- 1 | view: products { 2 | sql_table_name: products ;; 3 | 4 | dimension: id { 5 | primary_key: yes 6 | type: number 7 | sql: ${TABLE}.id ;; 8 | } 9 | 10 | dimension: category { 11 | sql: TRIM(${TABLE}.category) ;; 12 | drill_fields: [item_name] 13 | } 14 | 15 | dimension: item_name { 16 | sql: TRIM(${TABLE}.name) ;; 17 | } 18 | 19 | dimension: brand { 20 | sql: TRIM(${TABLE}.brand) ;; 21 | 22 | link: { 23 | label: "Website" 24 | url: "http://www.google.com/search?q={{ value | encode_uri }}+clothes&btnI" 25 | icon_url: "http://www.google.com/s2/favicons?domain=www.{{ value | encode_uri }}.com" 26 | } 27 | 28 | link: { 29 | label: "Facebook" 30 | url: "http://www.google.com/search?q=site:facebook.com+{{ value | encode_uri }}+clothes&btnI" 31 | icon_url: "https://static.xx.fbcdn.net/rsrc.php/yl/r/H3nktOa7ZMg.ico" 32 | } 33 | 34 | link: { 35 | label: "{{value}} Analytics Dashboard" 36 | url: "/dashboards/8?Brand%20Name={{ value | encode_uri }}" 37 | icon_url: "http://www.looker.com/favicon.ico" 38 | } 39 | 40 | drill_fields: [category, item_name] 41 | } 42 | 43 | dimension: retail_price { 44 | type: number 45 | sql: ${TABLE}.retail_price ;; 46 | } 47 | 48 | dimension: department { 49 | sql: TRIM(${TABLE}.department) ;; 50 | } 51 | 52 | dimension: sku { 53 | sql: ${TABLE}.sku ;; 54 | } 55 | 56 | dimension: distribution_center_id { 57 | type: number 58 | sql: ${TABLE}.distribution_center_id ;; 59 | } 60 | 61 | ## MEASURES ## 62 | 63 | measure: count { 64 | type: count 65 | drill_fields: [detail*] 66 | } 67 | 68 | measure: brand_count { 69 | type: count_distinct 70 | sql: ${brand} ;; 71 | drill_fields: [brand, detail2*, -brand_count] # show the brand, a bunch of counts (see the set below), don't show the brand count, because it will always be 1 72 | } 73 | 74 | measure: category_count { 75 | alias: [category.count] 76 | type: count_distinct 77 | sql: ${category} ;; 78 | drill_fields: [category, detail2*, -category_count] # don't show because it will always be 1 79 | } 80 | 81 | measure: department_count { 82 | alias: [department.count] 83 | type: count_distinct 84 | sql: ${department} ;; 85 | drill_fields: [department, detail2*, -department_count] # don't show because it will always be 1 86 | } 87 | 88 | set: detail { 89 | fields: [id, item_name, brand, category, department, retail_price, customers.count, orders.count, order_items.count, inventory_items.count] 90 | } 91 | 92 | set: detail2 { 93 | fields: [category_count, brand_count, department_count, count, customers.count, orders.count, order_items.count, inventory_items.count, products.count] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test_files/05_distribution_centers.view.lkml: -------------------------------------------------------------------------------- 1 | view: distribution_centers { 2 | dimension: location { 3 | type: location 4 | sql_latitude: ${TABLE}.latitude ;; 5 | sql_longitude: ${TABLE}.longitude ;; 6 | } 7 | 8 | dimension: id { 9 | type: number 10 | primary_key: yes 11 | sql: ${TABLE}.id ;; 12 | } 13 | 14 | dimension: name { 15 | sql: ${TABLE}.name ;; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test_files/11_order_facts.view.lkml: -------------------------------------------------------------------------------- 1 | include: "thelook.model.lkml" 2 | view: order_facts { 3 | derived_table: { 4 | explore_source: order_items { 5 | column: order_id {} 6 | column: items_in_order { field: order_items.count } 7 | column: order_amount { field: order_items.total_sale_price } 8 | column: order_cost { field: inventory_items.total_cost } 9 | column: user_id {field: order_items.user_id } 10 | column: created_at {field: order_items.created_raw} 11 | column: order_gross_margin {field: order_items.total_gross_margin} 12 | derived_column: order_sequence_number { 13 | sql: RANK() OVER (PARTITION BY user_id ORDER BY created_at) ;; 14 | } 15 | } 16 | sortkeys: ["order_id"] 17 | distribution: "order_id" 18 | datagroup_trigger: ecommerce_etl 19 | } 20 | dimension: order_id { 21 | type: number 22 | hidden: yes 23 | primary_key: yes 24 | sql: ${TABLE}.order_id ;; 25 | } 26 | 27 | dimension: items_in_order { 28 | type: number 29 | sql: ${TABLE}.items_in_order ;; 30 | } 31 | 32 | dimension: order_amount { 33 | type: number 34 | value_format_name: usd 35 | sql: ${TABLE}.order_amount ;; 36 | } 37 | 38 | dimension: order_cost { 39 | type: number 40 | value_format_name: usd 41 | sql: ${TABLE}.order_cost ;; 42 | } 43 | 44 | dimension: order_gross_margin { 45 | type: number 46 | value_format_name: usd 47 | } 48 | 49 | 50 | dimension: order_sequence_number { 51 | type: number 52 | sql: ${TABLE}.order_sequence_number ;; 53 | } 54 | 55 | dimension: is_first_purchase { 56 | type: yesno 57 | sql: ${order_sequence_number} = 1 ;; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test_files/12_user_order_facts.view.lkml: -------------------------------------------------------------------------------- 1 | view: user_order_facts { 2 | derived_table: { 3 | sql: SELECT 4 | user_id 5 | , COUNT(DISTINCT order_id) AS lifetime_orders 6 | , SUM(sale_price) AS lifetime_revenue 7 | , MIN(NULLIF(created_at,0)) AS first_order 8 | , MAX(NULLIF(created_at,0)) AS latest_order 9 | , COUNT(DISTINCT DATE_TRUNC('month', NULLIF(created_at,0))) AS number_of_distinct_months_with_orders 10 | FROM order_items 11 | GROUP BY user_id 12 | ;; 13 | sortkeys: ["user_id"] 14 | distribution: "user_id" 15 | datagroup_trigger: ecommerce_etl 16 | } 17 | 18 | dimension: user_id { 19 | primary_key: yes 20 | hidden: yes 21 | sql: ${TABLE}.user_id ;; 22 | } 23 | 24 | ##### Time and Cohort Fields ###### 25 | 26 | dimension_group: first_order { 27 | type: time 28 | timeframes: [date, week, month, year] 29 | sql: ${TABLE}.first_order ;; 30 | } 31 | 32 | dimension_group: latest_order { 33 | type: time 34 | timeframes: [date, week, month, year] 35 | sql: ${TABLE}.latest_order ;; 36 | } 37 | 38 | dimension: days_as_customer { 39 | description: "Days between first and latest order" 40 | type: number 41 | sql: DATEDIFF('day', ${TABLE}.first_order, ${TABLE}.latest_order)+1 ;; 42 | } 43 | 44 | dimension: days_as_customer_tiered { 45 | type: tier 46 | tiers: [0, 1, 7, 14, 21, 28, 30, 60, 90, 120] 47 | sql: ${days_as_customer} ;; 48 | style: integer 49 | } 50 | 51 | ##### Lifetime Behavior - Order Counts ###### 52 | 53 | dimension: lifetime_orders { 54 | type: number 55 | sql: ${TABLE}.lifetime_orders ;; 56 | } 57 | 58 | dimension: repeat_customer { 59 | description: "Lifetime Count of Orders > 1" 60 | type: yesno 61 | sql: ${lifetime_orders} > 1 ;; 62 | } 63 | 64 | dimension: lifetime_orders_tier { 65 | type: tier 66 | tiers: [0, 1, 2, 3, 5, 10] 67 | sql: ${lifetime_orders} ;; 68 | style: integer 69 | } 70 | 71 | measure: average_lifetime_orders { 72 | type: average 73 | value_format_name: decimal_2 74 | sql: ${lifetime_orders} ;; 75 | } 76 | 77 | dimension: distinct_months_with_orders { 78 | type: number 79 | sql: ${TABLE}.number_of_distinct_months_with_orders ;; 80 | } 81 | 82 | ##### Lifetime Behavior - Revenue ###### 83 | 84 | dimension: lifetime_revenue { 85 | type: number 86 | value_format_name: usd 87 | sql: ${TABLE}.lifetime_revenue ;; 88 | } 89 | 90 | dimension: lifetime_revenue_tier { 91 | type: tier 92 | tiers: [0, 25, 50, 100, 200, 500, 1000] 93 | sql: ${lifetime_revenue} ;; 94 | style: integer 95 | } 96 | 97 | measure: average_lifetime_revenue { 98 | type: average 99 | value_format_name: usd 100 | sql: ${lifetime_revenue} ;; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test_files/13_repeat_purchase_facts.view.lkml: -------------------------------------------------------------------------------- 1 | view: repeat_purchase_facts { 2 | derived_table: { 3 | sql: SELECT 4 | order_items.order_id 5 | , COUNT(DISTINCT repeat_order_items.id) AS number_subsequent_orders 6 | , MIN(repeat_order_items.created_at) AS next_order_date 7 | , MIN(repeat_order_items.order_id) AS next_order_id 8 | FROM order_items 9 | LEFT JOIN order_items repeat_order_items 10 | ON order_items.user_id = repeat_order_items.user_id 11 | AND order_items.created_at < repeat_order_items.created_at 12 | GROUP BY 1 13 | ;; 14 | sortkeys: ["order_id"] 15 | distribution: "order_id" 16 | datagroup_trigger: ecommerce_etl 17 | } 18 | 19 | dimension: order_id { 20 | type: number 21 | hidden: yes 22 | primary_key: yes 23 | sql: ${TABLE}.order_id ;; 24 | } 25 | 26 | dimension: next_order_id { 27 | type: number 28 | hidden: yes 29 | sql: ${TABLE}.next_order_id ;; 30 | } 31 | 32 | dimension: has_subsequent_order { 33 | type: yesno 34 | sql: ${next_order_id} > 0 ;; 35 | } 36 | 37 | dimension: number_subsequent_orders { 38 | type: number 39 | sql: ${TABLE}.number_subsequent_orders ;; 40 | } 41 | 42 | dimension_group: next_order { 43 | type: time 44 | timeframes: [raw, date] 45 | hidden: yes 46 | sql: ${TABLE}.next_order_date ;; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test_files/22_affinity.view.lkml: -------------------------------------------------------------------------------- 1 | view: affinity { 2 | derived_table: { 3 | datagroup_trigger: ecommerce_etl 4 | distribution: "product_a_id" 5 | sortkeys: ["product_a_id", "product_b_id"] 6 | sql: SELECT 7 | product_a_id 8 | , product_b_id 9 | , joint_user_freq 10 | , joint_order_freq 11 | , top1.prod_freq AS product_a_freq 12 | , top2.prod_freq AS product_b_freq 13 | 14 | FROM 15 | ( 16 | SELECT 17 | up1.prod_id AS product_a_id 18 | , up2.prod_id AS product_b_id 19 | , COUNT(*) AS joint_user_freq 20 | FROM ${user_order_product.SQL_TABLE_NAME} AS up1 21 | LEFT JOIN ${user_order_product.SQL_TABLE_NAME} AS up2 22 | ON up1.user_id = up2.user_id 23 | AND up1.prod_id <> up2.prod_id 24 | GROUP BY product_a_id, product_b_id 25 | ) AS juf 26 | 27 | LEFT JOIN 28 | ( 29 | SELECT 30 | op1.prod_id AS oproduct_a_id 31 | , op2.prod_id AS oproduct_b_id 32 | , COUNT(*) AS joint_order_freq 33 | FROM ${user_order_product.SQL_TABLE_NAME} op1 34 | LEFT JOIN ${user_order_product.SQL_TABLE_NAME} op2 35 | ON op1.order_id = op2.order_id 36 | AND op1.prod_id <> op2.prod_id 37 | GROUP BY oproduct_a_id, oproduct_b_id 38 | ) AS jof 39 | 40 | ON jof.oproduct_a_id = juf.product_a_id 41 | AND jof.oproduct_b_id = juf.product_b_id 42 | LEFT JOIN ${total_order_product.SQL_TABLE_NAME} top1 43 | ON top1.prod_id = juf.product_a_id 44 | LEFT JOIN ${total_order_product.SQL_TABLE_NAME} top2 45 | ON top2.prod_id = juf.product_b_id 46 | ;; 47 | } 48 | 49 | measure: count { 50 | type: count 51 | drill_fields: [detail*] 52 | } 53 | 54 | dimension: product_a_id { 55 | sql: ${TABLE}.product_a_id ;; 56 | } 57 | 58 | dimension: product_b_id { 59 | sql: ${TABLE}.product_b_id ;; 60 | } 61 | 62 | dimension: joint_user_freq { 63 | description: "The number of users who have purchased both product a and product b" 64 | type: number 65 | sql: ${TABLE}.joint_user_freq ;; 66 | } 67 | 68 | dimension: joint_order_freq { 69 | description: "The number of orders that include both product a and product b" 70 | type: number 71 | sql: ${TABLE}.joint_order_freq ;; 72 | } 73 | 74 | dimension: product_a_freq { 75 | description: "The total number of times product a has been purchased" 76 | type: number 77 | sql: ${TABLE}.product_a_freq ;; 78 | } 79 | 80 | dimension: product_b_freq { 81 | description: "The total number of times product b has been purchased" 82 | type: number 83 | sql: ${TABLE}.product_b_freq ;; 84 | } 85 | 86 | dimension: user_affinity { 87 | hidden: yes 88 | type: number 89 | sql: 1.0*${joint_user_freq}/NULLIF((${product_a_freq}+${product_b_freq})-(${joint_user_freq}),0) ;; 90 | value_format_name: percent_2 91 | } 92 | 93 | dimension: order_affinity { 94 | hidden: yes 95 | type: number 96 | sql: 1.0*${joint_order_freq}/NULLIF((${product_a_freq}+${product_b_freq})-(${joint_order_freq}),0) ;; 97 | value_format_name: percent_2 98 | } 99 | 100 | measure: avg_user_affinity { 101 | label: "Affinity Score (by User History)" 102 | description: "Percentage of users that bought both products weighted by how many times each product sold individually" 103 | type: average 104 | sql: 100.0 * ${user_affinity} ;; 105 | value_format_name: decimal_2 106 | } 107 | 108 | measure: avg_order_affinity { 109 | label: "Affinity Score (by Order Basket)" 110 | description: "Percentage of orders that contained both products weighted by how many times each product sold individually" 111 | type: average 112 | sql: 100.0 * ${order_affinity} ;; 113 | value_format_name: decimal_2 114 | } 115 | 116 | measure: combined_affinity { 117 | type: number 118 | sql: ${avg_user_affinity} + ${avg_order_affinity} ;; 119 | } 120 | 121 | set: detail { 122 | fields: [product_a_id,product_b_id,user_affinity,order_affinity] 123 | } 124 | } 125 | 126 | 127 | 128 | ############################################# 129 | #Table that aggregates the products purchased by user and order id 130 | view: user_order_product { 131 | derived_table: { 132 | datagroup_trigger: ecommerce_etl 133 | distribution: "prod_id" 134 | sortkeys: ["prod_id", "user_id", "order_id"] 135 | sql: SELECT 136 | oi.user_id AS user_id 137 | , p.id AS prod_id 138 | , oi.order_id AS order_id 139 | FROM order_items oi 140 | LEFT JOIN inventory_items ii 141 | ON oi.inventory_item_id = ii.id 142 | LEFT JOIN products p 143 | ON ii.product_id = p.id 144 | GROUP BY 1,2,3 145 | ;; 146 | } 147 | 148 | # measure: count { 149 | # type: count 150 | # drill_fields: [detail*] 151 | # } 152 | 153 | dimension: user_id { 154 | type: number 155 | sql: ${TABLE}.user_id ;; 156 | } 157 | 158 | dimension: prod_id { 159 | type: number 160 | sql: ${TABLE}.prod_id ;; 161 | } 162 | 163 | dimension: order_id { 164 | type: number 165 | sql: ${TABLE}.order_id ;; 166 | } 167 | } 168 | 169 | ################################################# 170 | #Table to count the total times a product id has been purchased 171 | view: total_order_product { 172 | derived_table: { 173 | datagroup_trigger: ecommerce_etl 174 | distribution: "prod_id" 175 | sortkeys: ["prod_id"] 176 | sql: SELECT 177 | p.id AS prod_id 178 | , COUNT(*) AS prod_freq 179 | FROM order_items oi 180 | LEFT JOIN inventory_items 181 | ON oi.inventory_item_id = inventory_items.id 182 | LEFT JOIN products p 183 | ON inventory_items.product_id = p.id 184 | GROUP BY p.id 185 | ;; 186 | } 187 | 188 | # measure: count { 189 | # type: count 190 | # drill_fields: [detail*] 191 | # } 192 | 193 | dimension: prod_id { 194 | sql: ${TABLE}.prod_id ;; 195 | } 196 | 197 | dimension: prod_freq { 198 | type: number 199 | sql: ${TABLE}.prod_freq ;; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /test_files/23_share_of_wallet.view.lkml: -------------------------------------------------------------------------------- 1 | view: order_items_share_of_wallet { 2 | view_label: "Share of Wallet" 3 | # 4 | # - measure: total_sale_price 5 | # type: sum 6 | # value_format: '$#,###' 7 | # sql: ${sale_price} 8 | # 9 | 10 | 11 | ########## Comparison for Share of Wallet ########## 12 | 13 | filter: item_name { 14 | view_label: "Share of Wallet (Item Level)" 15 | suggest_dimension: products.item_name 16 | suggest_explore: orders_with_share_of_wallet_application 17 | } 18 | 19 | filter: brand { 20 | view_label: "Share of Wallet (Brand Level)" 21 | suggest_dimension: products.brand 22 | suggest_explore: orders_with_share_of_wallet_application 23 | } 24 | 25 | dimension: primary_key { 26 | sql: ${order_items.id} ;; 27 | primary_key: yes 28 | hidden: yes 29 | } 30 | 31 | dimension: item_comparison { 32 | view_label: "Share of Wallet (Item Level)" 33 | description: "Compare a selected item vs. other items in the brand vs. all other brands" 34 | sql: CASE 35 | WHEN {% condition item_name %} trim(products.item_name) {% endcondition %} 36 | THEN '(1) '||${products.item_name} 37 | WHEN {% condition brand %} trim(products.brand) {% endcondition %} 38 | THEN '(2) Rest of '||${products.brand} 39 | ELSE '(3) Rest of Population' 40 | END 41 | ;; 42 | } 43 | 44 | dimension: brand_comparison { 45 | view_label: "Share of Wallet (Brand Level)" 46 | description: "Compare a selected brand vs. all other brands" 47 | sql: CASE 48 | WHEN {% condition brand %} trim(products.brand) {% endcondition %} 49 | THEN '(1) '||${products.brand} 50 | ELSE '(2) Rest of Population' 51 | END 52 | ;; 53 | } 54 | 55 | measure: total_sale_price_this_item { 56 | view_label: "Share of Wallet (Item Level)" 57 | type: sum 58 | hidden: yes 59 | sql: ${order_items.sale_price} ;; 60 | value_format_name: usd 61 | 62 | filters: { 63 | field: order_items_share_of_wallet.item_comparison 64 | value: "(1)%" 65 | } 66 | } 67 | 68 | measure: total_sale_price_this_brand { 69 | view_label: "Share of Wallet (Item Level)" 70 | type: sum 71 | hidden: yes 72 | value_format_name: usd 73 | sql: ${order_items.sale_price} ;; 74 | 75 | filters: { 76 | field: order_items_share_of_wallet.item_comparison 77 | value: "(2)%,(1)%" 78 | } 79 | } 80 | 81 | measure: total_sale_price_brand_v2 { 82 | view_label: "Share of Wallet (Brand Level)" 83 | label: "Total Sales - This Brand" 84 | type: sum 85 | value_format_name: usd 86 | sql: ${order_items.sale_price} ;; 87 | 88 | filters: { 89 | field: order_items_share_of_wallet.brand_comparison 90 | value: "(1)%" 91 | } 92 | } 93 | 94 | measure: item_share_of_wallet_within_brand { 95 | view_label: "Share of Wallet (Item Level)" 96 | type: number 97 | description: "This item sales over all sales for same brand" 98 | # view_label: 'Share of Wallet' 99 | value_format_name: percent_2 100 | sql: ${total_sale_price_this_item}*1.0 / nullif(${total_sale_price_this_brand},0) ;; 101 | } 102 | 103 | measure: item_share_of_wallet_within_company { 104 | view_label: "Share of Wallet (Item Level)" 105 | description: "This item sales over all sales across website" 106 | value_format_name: percent_2 107 | # view_label: 'Share of Wallet' 108 | type: number 109 | sql: ${total_sale_price_this_item}*1.0 / nullif(${order_items.total_sale_price},0) ;; 110 | } 111 | 112 | measure: brand_share_of_wallet_within_company { 113 | view_label: "Share of Wallet (Brand Level)" 114 | description: "This brand's sales over all sales across website" 115 | value_format_name: percent_2 116 | # view_label: 'Share of Wallet' 117 | type: number 118 | sql: ${total_sale_price_brand_v2}*1.0 / nullif(${order_items.total_sale_price},0) ;; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test_files/24_inventory_snapshot.view.lkml: -------------------------------------------------------------------------------- 1 | view: inventory_snapshot { 2 | derived_table: { 3 | datagroup_trigger: ecommerce_etl 4 | sortkeys: ["snapshot_date"] 5 | distribution: "product_id" 6 | sql: with calendar as 7 | (select distinct date(created_at) as snapshot_date 8 | from inventory_items 9 | -- where dateadd('day',90,created_at)>=current_date 10 | ) 11 | 12 | select 13 | 14 | inventory_items.product_id 15 | ,calendar.snapshot_date 16 | ,count(*) as number_in_stock 17 | 18 | from inventory_items 19 | left join calendar 20 | on inventory_items.created_at <= calendar.snapshot_date 21 | and (inventory_items.sold_at >= calendar.snapshot_date OR inventory_items.sold_at is null) 22 | -- where dateadd('day',90,calendar.snapshot_date)>=current_date 23 | group by 1,2 24 | 25 | ;; 26 | } 27 | 28 | 29 | 30 | dimension: product_id { 31 | type: number 32 | sql: ${TABLE}.product_id ;; 33 | } 34 | 35 | dimension: snapshot_date { 36 | type: date 37 | sql: ${TABLE}.snapshot_date ;; 38 | } 39 | 40 | dimension: number_in_stock { 41 | type: number 42 | hidden: yes 43 | sql: ${TABLE}.number_in_stock ;; 44 | } 45 | 46 | measure: total_in_stock { 47 | type: sum 48 | sql: ${number_in_stock} ;; 49 | } 50 | 51 | measure: stock_coverage_ratio { 52 | type: number 53 | sql: 1.0 * ${total_in_stock} / nullif(${trailing_sales_snapshot.sum_trailing_28d_sales},0) ;; 54 | value_format_name: decimal_2 55 | } 56 | 57 | measure: sum_stock_yesterday { 58 | type: sum 59 | hidden: yes 60 | sql: ${number_in_stock} ;; 61 | filters: { 62 | field: snapshot_date 63 | value: "yesterday" 64 | } 65 | } 66 | 67 | 68 | measure: sum_stock_last_wk { 69 | type: sum 70 | hidden: yes 71 | sql: ${number_in_stock} ;; 72 | filters: { 73 | field: snapshot_date 74 | value: "8 days ago for 1 day" 75 | } 76 | } 77 | 78 | measure: stock_coverage_ratio_yday { 79 | type: number 80 | view_label: "Stock Ratio Changes" 81 | sql: 1.0 * ${sum_stock_yesterday} / nullif(${trailing_sales_snapshot.sum_trailing_28d_sales_yesterday},0) ;; 82 | value_format_name: decimal_2 83 | } 84 | 85 | 86 | measure: stock_coverage_ratio_last_wk { 87 | type: number 88 | view_label: "Stock Ratio Changes" 89 | sql: 1.0 * ${sum_stock_last_wk} / nullif(${trailing_sales_snapshot.sum_trailing_28d_sales_last_wk},0) ;; 90 | value_format_name: decimal_2 91 | } 92 | 93 | measure: wk_to_wk_change_coverage { 94 | label: "WoW Change - Coverage Ratio" 95 | view_label: "Stock Ratio Changes" 96 | sql: round(100*(${stock_coverage_ratio_yday}-${stock_coverage_ratio_last_wk}),1) ;; 97 | value_format_name: decimal_1 98 | # value_format: "# 'bp'" 99 | } 100 | 101 | 102 | set: detail { 103 | fields: [product_id, snapshot_date, number_in_stock] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test_files/25_trailing_sales_snapshot.view.lkml: -------------------------------------------------------------------------------- 1 | view: trailing_sales_snapshot { 2 | derived_table: { 3 | datagroup_trigger: ecommerce_etl 4 | sortkeys: ["product_id"] 5 | distribution: "product_id" 6 | sql: with calendar as 7 | (select distinct date(created_at) as snapshot_date 8 | from inventory_items 9 | -- where dateadd('day',90,created_at)>=current_date 10 | ) 11 | 12 | select 13 | 14 | 15 | inventory_items.product_id 16 | ,date(order_items.created_at) as snapshot_date 17 | ,count(*) as trailing_28d_sales 18 | 19 | from order_items 20 | left join inventory_items on order_items.inventory_item_id = inventory_items.id 21 | left join calendar 22 | on order_items.created_at <= dateadd('day',28,calendar.snapshot_date) 23 | and order_items.created_at >= calendar.snapshot_date 24 | -- where dateadd('day',90,calendar.snapshot_date)>=current_date 25 | group by 1,2 26 | 27 | ;; 28 | } 29 | 30 | # measure: count { 31 | # type: count 32 | # drill_fields: [detail*] 33 | # } 34 | 35 | dimension: product_id { 36 | type: number 37 | sql: ${TABLE}.product_id ;; 38 | } 39 | 40 | dimension: snapshot_date { 41 | type: date 42 | sql: ${TABLE}.snapshot_date ;; 43 | } 44 | 45 | dimension: trailing_28d_sales { 46 | type: number 47 | hidden: yes 48 | sql: ${TABLE}.trailing_28d_sales ;; 49 | } 50 | 51 | measure: sum_trailing_28d_sales { 52 | type: sum 53 | sql: ${trailing_28d_sales} ;; 54 | } 55 | 56 | measure: sum_trailing_28d_sales_yesterday { 57 | type: sum 58 | hidden: yes 59 | sql: ${trailing_28d_sales} ;; 60 | filters: { 61 | field: snapshot_date 62 | value: "yesterday" 63 | } 64 | } 65 | 66 | 67 | measure: sum_trailing_28d_sales_last_wk { 68 | type: sum 69 | hidden: yes 70 | sql: ${trailing_28d_sales} ;; 71 | filters: { 72 | field: snapshot_date 73 | value: "8 days ago for 1 day" 74 | } 75 | } 76 | 77 | set: detail { 78 | fields: [product_id, snapshot_date, trailing_28d_sales] 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test_files/51_events.view.lkml: -------------------------------------------------------------------------------- 1 | view: events { 2 | sql_table_name: events ;; 3 | 4 | dimension: event_id { 5 | type: number 6 | primary_key: yes 7 | sql: ${TABLE}.id ;; 8 | } 9 | 10 | dimension: session_id { 11 | type: number 12 | hidden: yes 13 | sql: ${TABLE}.session_id ;; 14 | } 15 | 16 | dimension: ip { 17 | label: "IP Address" 18 | view_label: "Visitors" 19 | sql: ${TABLE}.ip_address ;; 20 | } 21 | 22 | dimension: user_id { 23 | sql: ${TABLE}.user_id ;; 24 | } 25 | 26 | dimension_group: event { 27 | type: time 28 | # timeframes: [time, date, hour, time_of_day, hour_of_day, week, day_of_week_index, day_of_week] 29 | sql: ${TABLE}.created_at ;; 30 | } 31 | 32 | dimension: sequence_number { 33 | type: number 34 | description: "Within a given session, what order did the events take place in? 1=First, 2=Second, etc" 35 | sql: ${TABLE}.sequence_number ;; 36 | } 37 | 38 | dimension: is_entry_event { 39 | type: yesno 40 | description: "Yes indicates this was the entry point / landing page of the session" 41 | sql: ${sequence_number} = 1 ;; 42 | } 43 | 44 | dimension: is_exit_event { 45 | type: yesno 46 | label: "UTM Source" 47 | sql: ${sequence_number} = ${sessions.number_of_events_in_session} ;; 48 | description: "Yes indicates this was the exit point / bounce page of the session" 49 | } 50 | 51 | measure: count_bounces { 52 | type: count 53 | description: "Count of events where those events were the bounce page for the session" 54 | 55 | filters: { 56 | field: is_exit_event 57 | value: "Yes" 58 | } 59 | } 60 | 61 | measure: bounce_rate { 62 | type: number 63 | value_format_name: percent_2 64 | description: "Percent of events where those events were the bounce page for the session, out of all events" 65 | sql: ${count_bounces}*1.0 / nullif(${count}*1.0,0) ;; 66 | } 67 | 68 | dimension: full_page_url { 69 | sql: ${TABLE}.uri ;; 70 | } 71 | 72 | dimension: viewed_product_id { 73 | type: number 74 | sql: CASE 75 | WHEN ${event_type} = 'Product' THEN right(${full_page_url},len(${full_page_url})-9) 76 | END 77 | ;; 78 | } 79 | 80 | dimension: event_type { 81 | sql: ${TABLE}.event_type ;; 82 | } 83 | 84 | dimension: funnel_step { 85 | description: "Login -> Browse -> Add to Cart -> Checkout" 86 | sql: CASE 87 | WHEN ${event_type} IN ('Login', 'Home') THEN '(1) Land' 88 | WHEN ${event_type} IN ('Category', 'Brand') THEN '(2) Browse Inventory' 89 | WHEN ${event_type} = 'Product' THEN '(3) View Product' 90 | WHEN ${event_type} = 'Cart' THEN '(4) Add Item to Cart' 91 | WHEN ${event_type} = 'Purchase' THEN '(5) Purchase' 92 | END 93 | ;; 94 | } 95 | 96 | measure: unique_visitors { 97 | type: count_distinct 98 | description: "Uniqueness determined by IP Address and User Login" 99 | view_label: "Visitors" 100 | sql: ${ip} ;; 101 | drill_fields: [visitors*] 102 | } 103 | 104 | dimension: location { 105 | type: location 106 | view_label: "Visitors" 107 | sql_latitude: ${TABLE}.latitude ;; 108 | sql_longitude: ${TABLE}.longitude ;; 109 | } 110 | 111 | dimension: approx_location { 112 | type: location 113 | view_label: "Visitors" 114 | sql_latitude: round(${TABLE}.latitude,1) ;; 115 | sql_longitude: round(${TABLE}.longitude,1) ;; 116 | } 117 | 118 | dimension: has_user_id { 119 | type: yesno 120 | view_label: "Visitors" 121 | description: "Did the visitor sign in as a website user?" 122 | sql: ${users.id} > 0 ;; 123 | } 124 | 125 | dimension: browser { 126 | view_label: "Visitors" 127 | sql: ${TABLE}.browser ;; 128 | } 129 | 130 | dimension: os { 131 | label: "Operating System" 132 | view_label: "Visitors" 133 | sql: ${TABLE}.os ;; 134 | } 135 | 136 | measure: count { 137 | type: count 138 | drill_fields: [simple_page_info*] 139 | } 140 | 141 | measure: sessions_count { 142 | type: count_distinct 143 | sql: ${session_id} ;; 144 | } 145 | 146 | measure: count_m { 147 | label: "Count (MM)" 148 | type: number 149 | hidden: yes 150 | sql: ${count}/1000000.0 ;; 151 | drill_fields: [simple_page_info*] 152 | value_format: "#.### \"M\"" 153 | } 154 | 155 | measure: unique_visitors_m { 156 | label: "Unique Visitors (MM)" 157 | view_label: "Visitors" 158 | type: number 159 | sql: count (distinct ${ip}) / 1000000.0 ;; 160 | description: "Uniqueness determined by IP Address and User Login" 161 | value_format: "#.### \"M\"" 162 | hidden: yes 163 | drill_fields: [visitors*] 164 | } 165 | 166 | measure: unique_visitors_k { 167 | label: "Unique Visitors (k)" 168 | view_label: "Visitors" 169 | type: number 170 | hidden: yes 171 | description: "Uniqueness determined by IP Address and User Login" 172 | sql: count (distinct ${ip}) / 1000.0 ;; 173 | value_format: "#.### \"k\"" 174 | drill_fields: [visitors*] 175 | } 176 | 177 | set: simple_page_info { 178 | fields: [event_id, event_time, event_type, 179 | # - os 180 | # - browser 181 | full_page_url, user_id, funnel_step] 182 | } 183 | 184 | set: visitors { 185 | fields: [ip, os, browser, user_id, count] 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /test_files/52_sessions.view.lkml: -------------------------------------------------------------------------------- 1 | view: sessions { 2 | derived_table: { 3 | sortkeys: ["session_id"] 4 | distribution: "session_id" 5 | datagroup_trigger: ecommerce_etl 6 | sql: SELECT 7 | session_id 8 | , MIN(created_at) AS session_start 9 | , MAX(created_at) AS session_end 10 | , COUNT(*) AS number_of_events_in_session 11 | , SUM(CASE WHEN event_type IN ('Category','Brand') THEN 1 END) AS browse_events 12 | , SUM(CASE WHEN event_type = 'Product' THEN 1 END) AS product_events 13 | , SUM(CASE WHEN event_type = 'Cart' THEN 1 END) AS cart_events 14 | , SUM(CASE WHEN event_type = 'Purchase' THEN 1 end) AS purchase_events 15 | , MAX(user_id) AS session_user_id 16 | , MIN(id) AS landing_event_id 17 | , MAX(id) AS bounce_event_id 18 | FROM events 19 | GROUP BY session_id 20 | ;; 21 | } 22 | 23 | ##### Basic Web Info ######## 24 | 25 | measure: count { 26 | type: count 27 | drill_fields: [detail*] 28 | } 29 | 30 | dimension: session_id { 31 | type: string 32 | primary_key: yes 33 | sql: ${TABLE}.session_id ;; 34 | } 35 | 36 | dimension: session_user_id { 37 | sql: ${TABLE}.session_user_id ;; 38 | } 39 | 40 | dimension: landing_event_id { 41 | sql: ${TABLE}.landing_event_id ;; 42 | } 43 | 44 | dimension: bounce_event_id { 45 | sql: ${TABLE}.bounce_event_id ;; 46 | } 47 | 48 | dimension_group: session_start { 49 | type: time 50 | # timeframes: [time, date, week, month, hour_of_day, day_of_week] 51 | sql: ${TABLE}.session_start ;; 52 | } 53 | 54 | dimension_group: session_end { 55 | type: time 56 | timeframes: [raw, time, date, week, month] 57 | sql: ${TABLE}.session_end ;; 58 | } 59 | 60 | dimension: duration { 61 | label: "Duration (sec)" 62 | type: number 63 | sql: DATEDIFF('second', ${session_start_raw}, ${session_end_raw}) ;; 64 | } 65 | 66 | measure: average_duration { 67 | label: "Average Duration (sec)" 68 | type: average 69 | value_format_name: decimal_2 70 | sql: ${duration} ;; 71 | } 72 | 73 | dimension: duration_seconds_tier { 74 | label: "Duration Tier (sec)" 75 | type: tier 76 | tiers: [10, 30, 60, 120, 300] 77 | style: integer 78 | sql: ${duration} ;; 79 | } 80 | 81 | ##### Bounce Information ######## 82 | 83 | dimension: is_bounce_session { 84 | type: yesno 85 | sql: ${number_of_events_in_session} = 1 ;; 86 | } 87 | 88 | measure: count_bounce_sessions { 89 | type: count 90 | 91 | filters: { 92 | field: is_bounce_session 93 | value: "Yes" 94 | } 95 | 96 | drill_fields: [detail*] 97 | } 98 | 99 | measure: percent_bounce_sessions { 100 | type: number 101 | value_format_name: percent_2 102 | sql: 1.0 * ${count_bounce_sessions} / nullif(${count},0) ;; 103 | } 104 | 105 | ####### Session by event types included ######## 106 | 107 | dimension: number_of_browse_events_in_session { 108 | type: number 109 | hidden: yes 110 | sql: ${TABLE}.browse_events ;; 111 | } 112 | 113 | dimension: number_of_product_events_in_session { 114 | type: number 115 | hidden: yes 116 | sql: ${TABLE}.product_events ;; 117 | } 118 | 119 | dimension: number_of_cart_events_in_session { 120 | type: number 121 | hidden: yes 122 | sql: ${TABLE}.cart_events ;; 123 | } 124 | 125 | dimension: number_of_purchase_events_in_session { 126 | type: number 127 | hidden: yes 128 | sql: ${TABLE}.purchase_events ;; 129 | } 130 | 131 | dimension: includes_browse { 132 | type: yesno 133 | sql: ${number_of_browse_events_in_session} > 0 ;; 134 | } 135 | 136 | dimension: includes_product { 137 | type: yesno 138 | sql: ${number_of_product_events_in_session} > 0 ;; 139 | } 140 | 141 | dimension: includes_cart { 142 | type: yesno 143 | sql: ${number_of_cart_events_in_session} > 0 ;; 144 | } 145 | 146 | dimension: includes_purchase { 147 | type: yesno 148 | sql: ${number_of_purchase_events_in_session} > 0 ;; 149 | } 150 | 151 | measure: count_with_cart { 152 | type: count 153 | 154 | filters: { 155 | field: includes_cart 156 | value: "Yes" 157 | } 158 | 159 | drill_fields: [detail*] 160 | } 161 | 162 | measure: count_with_purchase { 163 | type: count 164 | 165 | filters: { 166 | field: includes_purchase 167 | value: "Yes" 168 | } 169 | 170 | drill_fields: [detail*] 171 | } 172 | 173 | dimension: number_of_events_in_session { 174 | type: number 175 | sql: ${TABLE}.number_of_events_in_session ;; 176 | } 177 | 178 | ####### Linear Funnel ######## 179 | 180 | dimension: furthest_funnel_step { 181 | sql: CASE 182 | WHEN ${number_of_purchase_events_in_session} > 0 THEN '(5) Purchase' 183 | WHEN ${number_of_cart_events_in_session} > 0 THEN '(4) Add to Cart' 184 | WHEN ${number_of_product_events_in_session} > 0 THEN '(3) View Product' 185 | WHEN ${number_of_browse_events_in_session} > 0 THEN '(2) Browse' 186 | ELSE '(1) Land' 187 | END 188 | ;; 189 | } 190 | 191 | measure: all_sessions { 192 | view_label: "Funnel View" 193 | label: "(1) All Sessions" 194 | type: count 195 | drill_fields: [detail*] 196 | } 197 | 198 | measure: count_browse_or_later { 199 | view_label: "Funnel View" 200 | label: "(2) Browse or later" 201 | type: count 202 | 203 | filters: { 204 | field: furthest_funnel_step 205 | value: "(2) Browse,(3) View Product,(4) Add to Cart,(5) Purchase 206 | " 207 | } 208 | 209 | drill_fields: [detail*] 210 | } 211 | 212 | measure: count_product_or_later { 213 | view_label: "Funnel View" 214 | label: "(3) View Product or later" 215 | type: count 216 | 217 | filters: { 218 | field: furthest_funnel_step 219 | value: "(3) View Product,(4) Add to Cart,(5) Purchase 220 | " 221 | } 222 | 223 | drill_fields: [detail*] 224 | } 225 | 226 | measure: count_cart_or_later { 227 | view_label: "Funnel View" 228 | label: "(4) Add to Cart or later" 229 | type: count 230 | 231 | filters: { 232 | field: furthest_funnel_step 233 | value: "(4) Add to Cart,(5) Purchase 234 | " 235 | } 236 | 237 | drill_fields: [detail*] 238 | } 239 | 240 | measure: count_purchase { 241 | view_label: "Funnel View" 242 | label: "(5) Purchase" 243 | type: count 244 | 245 | filters: { 246 | field: furthest_funnel_step 247 | value: "(5) Purchase 248 | " 249 | } 250 | 251 | drill_fields: [detail*] 252 | } 253 | 254 | measure: cart_to_checkout_conversion { 255 | view_label: "Funnel View" 256 | type: number 257 | value_format_name: percent_2 258 | sql: 1.0 * ${count_purchase} / nullif(${count_cart_or_later},0) ;; 259 | } 260 | 261 | measure: overall_conversion { 262 | view_label: "Funnel View" 263 | type: number 264 | value_format_name: percent_2 265 | sql: 1.0 * ${count_purchase} / nullif(${count},0) ;; 266 | } 267 | 268 | set: detail { 269 | fields: [session_id, session_start_time, session_end_time, number_of_events_in_session, duration, number_of_purchase_events_in_session, number_of_cart_events_in_session] 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /test_files/97_web_analytics_data_tool.view.lkml: -------------------------------------------------------------------------------- 1 | include: "*.view" # include all the views 2 | 3 | 4 | ######################################################################################## 5 | # Web Analytics Tool Explore 6 | ######################################################################################## 7 | explore: data_tool { 8 | hidden: yes 9 | label: "Web Analytics Data Tool" 10 | 11 | join: sessions { 12 | from: sessions_webanalytics 13 | fields: [sessions.session_id, sessions.session_start_date, sessions.is_bounce_session, sessions.count_purchase, session_end_date, sessions.duration, sessions.duration_seconds_tier, sessions.average_duration, sessions.count, sessions.count_bounce_sessions, sessions.percent_bounce_sessions, sessions.overall_conversion, sessions.furthest_funnel_step] 14 | sql_on: ${data_tool.session_id} = ${sessions.session_id} ;; 15 | relationship: many_to_one 16 | } 17 | 18 | join: users { 19 | sql_on: ${sessions.session_user_id} = ${users.id} ;; 20 | relationship: many_to_one 21 | } 22 | 23 | join: user_order_facts { 24 | sql_on: ${users.id} = ${user_order_facts.user_id} ;; 25 | relationship: one_to_one 26 | view_label: "Users" 27 | } 28 | } 29 | 30 | 31 | 32 | #################################################################################### 33 | # Dynamic Fields 34 | # https://looker.com/platform/blocks/data-tool/web-analytics 35 | #################################################################################### 36 | 37 | view: data_tool { 38 | extends: [events] 39 | view_label: "Events" 40 | 41 | 42 | parameter: timeframe_filter { 43 | view_label: "Data Tool" 44 | allowed_value: { value: "Date" } 45 | allowed_value: { value: "Week" } 46 | allowed_value: { value: "Month" } 47 | } 48 | 49 | dimension: timeframe { 50 | view_label: "Data Tool" 51 | sql: CASE 52 | WHEN {% parameter timeframe_filter %} = 'Date' THEN ${event_date}::varchar 53 | WHEN {% parameter timeframe_filter %} = 'Week' THEN ${event_week} 54 | WHEN {% parameter timeframe_filter %} = 'Month' THEN ${event_month} 55 | END ;; 56 | label_from_parameter: timeframe_filter 57 | drill_fields: [os, browser, event_type] 58 | } 59 | 60 | 61 | parameter: primary_metric_filter { 62 | view_label: "Data Tool" 63 | allowed_value: { value: "Users" } 64 | allowed_value: { value: "Visitors" } 65 | allowed_value: { value: "Sessions" } 66 | allowed_value: { value: "Orders" } 67 | default_value: "Users" 68 | } 69 | 70 | measure: primary_metric { 71 | type: number 72 | view_label: "Data Tool" 73 | sql: CASE 74 | WHEN {% parameter primary_metric_filter %} = 'Users' THEN ${users.count} 75 | WHEN {% parameter primary_metric_filter %} = 'Visitors' THEN ${unique_visitors} 76 | WHEN {% parameter primary_metric_filter %} = 'Sessions' THEN ${sessions.count} 77 | WHEN {% parameter primary_metric_filter %} = 'Orders' THEN ${sessions.count_purchase} 78 | END ;; 79 | label_from_parameter: primary_metric_filter 80 | drill_fields: [detail*] 81 | } 82 | 83 | parameter: second_metric_filter { 84 | view_label: "Data Tool" 85 | allowed_value: { value: "Bounces" } 86 | allowed_value: { value: "Bounce Rate" } 87 | allowed_value: { value: "Conversion Rate" } 88 | default_value: "Conversion Rate" 89 | } 90 | 91 | measure: second_metric { 92 | type: number 93 | view_label: "Data Tool" 94 | sql: CASE 95 | WHEN {% parameter second_metric_filter %} = 'Bounces' THEN ${sessions.count_bounce_sessions} 96 | WHEN {% parameter second_metric_filter %} = 'Bounce Rate' THEN round((100.0 * ${sessions.percent_bounce_sessions}),2) 97 | WHEN {% parameter second_metric_filter %} = 'Conversion Rate' THEN round((100.0 * ${sessions.conversion_rate}),2) 98 | END ;; 99 | html: {% if metric_name._value contains 'Rate' or metric_name._value contains 'Users' %} 100 | {{ linked_value }}{{ format_symbol._value }} 101 | {% else %} 102 | {{ format_symbol._value }}{{ linked_value }} 103 | {% endif %} ;; 104 | label_from_parameter: second_metric_filter 105 | drill_fields: [detail*] 106 | } 107 | 108 | 109 | set: detail { 110 | fields: [ 111 | event_date, 112 | users.count, 113 | unique_visitors, 114 | sessions.count, 115 | sessions.count_bounce_sessions, 116 | sessions.percent_bounce_sessions, 117 | sessions.conversion_rate, 118 | sessions.orders 119 | ] 120 | } 121 | 122 | 123 | 124 | 125 | ################################################################ 126 | # Used for dynamically applying a format to the metric parameter 127 | ################################################################ 128 | dimension: metric_name { 129 | hidden: yes 130 | type: string 131 | sql: CASE 132 | WHEN {% parameter second_metric_filter %} = 'Bounces' THEN 'Bounces' 133 | WHEN {% parameter second_metric_filter %} = 'Bounce Rate' THEN 'Bounce Rate' 134 | WHEN {% parameter second_metric_filter %} = 'Conversion Rate' THEN 'Conversion Rate' 135 | WHEN {% parameter second_metric_filter %} = '% New Users' THEN '% New Users' 136 | ELSE NULL 137 | END ;; 138 | } 139 | 140 | dimension: format_symbol { 141 | hidden: yes 142 | sql: 143 | CASE 144 | WHEN ${metric_name} IN ('Bounce Rate','Conversion Rate','% New Users') THEN '%' 145 | END ;; 146 | } 147 | 148 | 149 | 150 | } 151 | 152 | 153 | 154 | 155 | 156 | 157 | ################################################################ 158 | # Change view labels for a few fields 159 | ################################################################ 160 | 161 | view: sessions_webanalytics { 162 | extends: [sessions] 163 | 164 | measure: conversion_rate { 165 | view_label: "Sessions" 166 | type: number 167 | value_format_name: percent_2 168 | sql: 1.0 * ${count_purchase} / nullif(${count},0) ;; 169 | drill_fields: [count_purchase, count, overall_conversion] 170 | } 171 | 172 | measure: count_purchase { 173 | view_label: "Sessions" 174 | label: "Orders" 175 | # hidden: yes 176 | type: count 177 | filters: { 178 | field: furthest_funnel_step 179 | value: "(5) Purchase" 180 | } 181 | drill_fields: [detail*] 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /test_files/98_cohort_data_tool.view.lkml: -------------------------------------------------------------------------------- 1 | include: "*.view" # include all the views 2 | 3 | 4 | ######################################################################################## 5 | # Cohort Data Tool Explore 6 | # dynamic join on cohort size depending on what cohort is selected by the user 7 | ######################################################################################## 8 | explore: cohorts { 9 | hidden: yes 10 | label: "Cohort Data Tool" 11 | view_name: cohort_size 12 | join: users { 13 | fields: [users.id, users.name, users.first_name, users.last_name, users.email, users.age, users.created_date, users.gender, users.traffic_source, users.count] 14 | type: left_outer 15 | relationship: many_to_one 16 | sql_on: 17 | CASE 18 | WHEN {% parameter cohort_size.cohort_filter %} = 'User Signup Month' 19 | THEN ${users.created_month} = ${cohort_size.cohort} 20 | WHEN {% parameter cohort_size.cohort_filter %} = 'Gender' 21 | THEN ${users.gender} = ${cohort_size.cohort} 22 | WHEN {% parameter cohort_size.cohort_filter %} = 'Age Group' 23 | THEN ${users.age_tier} = ${cohort_size.cohort} 24 | WHEN {% parameter cohort_size.cohort_filter %} = 'Traffic Source' 25 | THEN ${users.traffic_source} = ${cohort_size.cohort} 26 | ELSE ${users.created_month} = ${cohort_size.cohort} 27 | END 28 | ;; 29 | } 30 | join: order_items { 31 | from: order_items_cohorts 32 | fields: [order_items.id, order_items.order_id, order_items.created_date, order_items.total_sale_price, order_items.months_since_signup] 33 | type: left_outer 34 | relationship: one_to_many 35 | sql_on: ${users.id} = ${order_items.user_id} ;; 36 | } 37 | } 38 | 39 | 40 | ######################################################################################## 41 | # Define Cohort Size 42 | # dynamically swap grouping for user signup month, gender, age group, traffic source 43 | ######################################################################################## 44 | 45 | view: cohort_size { 46 | view_label: "Cohort" 47 | derived_table: { 48 | sql: SELECT 49 | CASE 50 | WHEN {% parameter cohort_filter %} = 'User Signup Month' 51 | THEN TO_CHAR(DATE_TRUNC('month', CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', users.created_at )), 'YYYY-MM') 52 | WHEN {% parameter cohort_filter %} = 'Gender' 53 | THEN users.gender 54 | WHEN {% parameter cohort_filter %} = 'Traffic Source' 55 | THEN users.traffic_source 56 | WHEN {% parameter cohort_filter %} = 'Age Group' 57 | THEN 58 | (CASE 59 | WHEN users.age < 0 THEN 'Below 0' 60 | WHEN users.age >= 0 AND users.age < 10 THEN '0 to 9' 61 | WHEN users.age >= 10 AND users.age < 20 THEN '10 to 19' 62 | WHEN users.age >= 20 AND users.age < 30 THEN '20 to 29' 63 | WHEN users.age >= 30 AND users.age < 40 THEN '30 to 39' 64 | WHEN users.age >= 40 AND users.age < 50 THEN '40 to 49' 65 | WHEN users.age >= 50 AND users.age < 60 THEN '50 to 59' 66 | WHEN users.age >= 60 AND users.age < 70 THEN '60 to 69' 67 | WHEN users.age >= 70 THEN '70 or Above' 68 | ELSE 'Undefined' 69 | END) 70 | ELSE TO_CHAR(DATE_TRUNC('month', CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', users.created_at )), 'YYYY-MM') 71 | END AS cohort, 72 | COUNT(DISTINCT users.id ) AS cohort_size 73 | FROM users AS users 74 | GROUP BY 1 75 | ;; 76 | } 77 | 78 | 79 | parameter: cohort_filter { 80 | label: "Cohort Picker" 81 | description: "Choose a cohort" 82 | allowed_value: { value: "User Signup Month" } 83 | allowed_value: { value: "Gender" } 84 | allowed_value: { value: "Age Group" } 85 | allowed_value: { value: "Traffic Source" } 86 | } 87 | 88 | parameter: metric_filter { 89 | label: "Metric Picker" 90 | description: "Choose a metric" 91 | allowed_value: { value: "User Retention" } 92 | allowed_value: { value: "Average Orders per User" } 93 | allowed_value: { value: "Average Spend per User" } 94 | # allowed_value: { value: "Cumulative Spend" } 95 | } 96 | 97 | measure: cohort_size { 98 | type: sum 99 | sql: ${TABLE}.cohort_size ;; 100 | drill_fields: [users.id, users.name, users.email, users.age, users.created_date, users.count] 101 | } 102 | 103 | dimension: cohort { 104 | description: "Use in conjuction with the Cohort Picker" 105 | primary_key: yes 106 | type: string 107 | sql: ${TABLE}.cohort ;; 108 | label_from_parameter: cohort_filter 109 | } 110 | 111 | measure: metric { 112 | type: number 113 | description: "Use in conjuction with the Metric Picker" 114 | sql: CASE 115 | WHEN {% parameter metric_filter %} = 'User Retention' THEN trunc(${percent_user_retention}) 116 | WHEN {% parameter metric_filter %} = 'Average Orders per User' THEN round(${average_orders_per_user},2) 117 | WHEN {% parameter metric_filter %} = 'Average Spend per User' THEN trunc(${average_spend_per_user}) 118 | ELSE NULL 119 | END ;; 120 | html: {% if metric_name._value contains 'User Retention' %} 121 | {{ linked_value }}{{ format_symbol._value }} 122 | {% else %} 123 | {{ format_symbol._value }}{{ linked_value }} 124 | {% endif %} ;; 125 | drill_fields: [cohort_size, percent_user_retention, users.count, average_orders_per_user, average_spend_per_user] 126 | label_from_parameter: metric_filter 127 | } 128 | 129 | # WHEN {% parameter metric_filter %} = 'Cumulative Spend' THEN trunc(${cumulative_spend}) 130 | 131 | 132 | measure: percent_user_retention { 133 | view_label: "Users" 134 | description: "number of active users / number of users in the cohort (use with months since signup dimension)" 135 | type: number 136 | sql: round(100.0 * ${users.count} / nullif(${cohort_size},0),2) ;; 137 | value_format: "0.00\%" 138 | drill_fields: [users.count, cohort_size, percent_user_retention] 139 | } 140 | 141 | measure: average_spend_per_user { 142 | view_label: "Order Items" 143 | type: number 144 | value_format_name: usd 145 | sql: 1.0 * ${order_items.total_sale_price} / NULLIF(${users.count},0) ;; 146 | drill_fields: [order_items.id, order_items.order_id, order_items.created_date, order_items.total_sale_price, users.email] 147 | } 148 | 149 | measure: average_orders_per_user { 150 | view_label: "Order Items" 151 | type: number 152 | value_format_name: usd 153 | sql: 1.0 * ${order_items.order_count} / NULLIF(${users.count},0) ;; 154 | drill_fields: [users.email, order_items.order_count] 155 | } 156 | 157 | # measure: cumulative_spend { 158 | # view_label: "Order Items" 159 | # type: running_total 160 | # direction: "column" 161 | # value_format_name: usd 162 | # sql: ${order_items.total_sale_price} ;; 163 | # drill_fields: [order_items.id, order_items.order_id, order_items.created_date, order_items.total_sale_price, users.email] 164 | # } 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | ################################################################ 199 | # Used for dynamically applying a format to the metric parameter 200 | ################################################################ 201 | 202 | dimension: metric_name { 203 | hidden: yes 204 | type: string 205 | sql: CASE 206 | WHEN {% parameter metric_filter %} = 'User Retention' THEN 'User Retention' 207 | WHEN {% parameter metric_filter %} = 'Average Orders per User' THEN 'Average Orders per User' 208 | WHEN {% parameter metric_filter %} = 'Average Spend per User' THEN 'Average Spend per User' 209 | WHEN {% parameter metric_filter %} = 'Cumulative Spend' THEN 'Cumulative Spend' 210 | ELSE NULL 211 | END ;; 212 | } 213 | 214 | dimension: format_symbol { 215 | hidden: yes 216 | sql: 217 | CASE 218 | WHEN ${metric_name} = 'User Retention' THEN '%' 219 | WHEN ${metric_name} = 'Average Spend per User' THEN '$' 220 | WHEN ${metric_name} = 'Cumulative Spend' THEN '$' 221 | END ;; 222 | } 223 | 224 | 225 | 226 | } 227 | 228 | 229 | ################################################################ 230 | # Group 'months since signup' under Order Items 231 | ################################################################ 232 | 233 | view: order_items_cohorts { 234 | extends: [order_items] 235 | 236 | dimension: months_since_signup { 237 | view_label: "Order Items" 238 | description: "Months an order occurred since the user first signed up" 239 | type: number 240 | sql: DATEDIFF('month',${users.created_raw},${created_raw}) ;; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /test_files/99_users_avatar.view.lkml: -------------------------------------------------------------------------------- 1 | include: "01_order_items.view.lkml" 2 | include: "02_users.view.lkml" 3 | ## kittens for certain demos 4 | 5 | view: kitten_users { 6 | extends: [users] 7 | 8 | dimension: portrait { 9 | label: "Kitten Portrait" 10 | sql: GREATEST(MOD(${id}*97,867),MOD(${id}*31,881),MOD(${id}*72,893)) ;; 11 | type: number 12 | html: 13 | ;; 14 | } 15 | 16 | dimension: name { 17 | label: "Kitten Name" 18 | sql: ${first_name} || ' ' || ${TABLE}.last_name ;; 19 | } 20 | 21 | dimension: first_name { 22 | label: "Kitten First Name" 23 | 24 | case: { 25 | when: { 26 | sql: MOD(${id},24) = 23 ;; 27 | label: "Bella" 28 | } 29 | 30 | when: { 31 | sql: MOD(${id},24) = 22 ;; 32 | label: "Bandit" 33 | } 34 | 35 | when: { 36 | sql: MOD(${id},24) = 21 ;; 37 | label: "Tigger" 38 | } 39 | 40 | when: { 41 | sql: MOD(${id},24) = 20 ;; 42 | label: "Boots" 43 | } 44 | 45 | when: { 46 | sql: MOD(${id},24) = 19 ;; 47 | label: "Chloe" 48 | } 49 | 50 | when: { 51 | sql: MOD(${id},24) = 18 ;; 52 | label: "Maggie" 53 | } 54 | 55 | when: { 56 | sql: MOD(${id},24) = 17 ;; 57 | label: "Pumpkin" 58 | } 59 | 60 | when: { 61 | sql: MOD(${id},24) = 16 ;; 62 | label: "Oliver" 63 | } 64 | 65 | when: { 66 | sql: MOD(${id},24) = 15 ;; 67 | label: "Sammy" 68 | } 69 | 70 | when: { 71 | sql: MOD(${id},24) = 14 ;; 72 | label: "Shadow" 73 | } 74 | 75 | when: { 76 | sql: MOD(${id},24) = 13 ;; 77 | label: "Sassy" 78 | } 79 | 80 | when: { 81 | sql: MOD(${id},24) = 12 ;; 82 | label: "Kitty" 83 | } 84 | 85 | when: { 86 | sql: MOD(${id},24) = 11 ;; 87 | label: "Snowball" 88 | } 89 | 90 | when: { 91 | sql: MOD(${id},24) = 10 ;; 92 | label: "Snickers" 93 | } 94 | 95 | when: { 96 | sql: MOD(${id},24) = 9 ;; 97 | label: "Socks" 98 | } 99 | 100 | when: { 101 | sql: MOD(${id},24) = 8 ;; 102 | label: "Gizmo" 103 | } 104 | 105 | when: { 106 | sql: MOD(${id},24) = 7 ;; 107 | label: "Jake" 108 | } 109 | 110 | when: { 111 | sql: MOD(${id},24) = 6 ;; 112 | label: "Lily" 113 | } 114 | 115 | when: { 116 | sql: MOD(${id},24) = 5 ;; 117 | label: "Charlie" 118 | } 119 | 120 | when: { 121 | sql: MOD(${id},24) = 4 ;; 122 | label: "Peanut" 123 | } 124 | 125 | when: { 126 | sql: MOD(${id},24) = 3 ;; 127 | label: "Zoe" 128 | } 129 | 130 | when: { 131 | sql: MOD(${id},24) = 2 ;; 132 | label: "Felix" 133 | } 134 | 135 | when: { 136 | sql: MOD(${id},24) = 1 ;; 137 | label: "Mimi" 138 | } 139 | 140 | when: { 141 | sql: MOD(${id},24) = 0 ;; 142 | label: "Jasmine" 143 | } 144 | } 145 | } 146 | 147 | # set: detail { 148 | # fields: [EXTENDED*, portrait] 149 | # } 150 | } 151 | 152 | view: kitten_order_items { 153 | extends: [order_items] 154 | 155 | # set: detail { 156 | # fields: [EXTENDED*, users.portrait] 157 | # } 158 | } 159 | -------------------------------------------------------------------------------- /test_files/thelook.model.lkml: -------------------------------------------------------------------------------- 1 | connection: "demonew_events_ecommerce" 2 | label: "1) eCommerce with Event Data" 3 | include: "*.view" # include all the views 4 | include: "*.dashboard" # include all the dashboards 5 | 6 | datagroup: ecommerce_etl { 7 | sql_trigger: SELECT max(completed_at) FROM public.etl_jobs ;; 8 | max_cache_age: "24 hours"} 9 | persist_with: ecommerce_etl 10 | ############ Base Explores ############# 11 | 12 | explore: order_items { 13 | label: "(1) Orders, Items and Users" 14 | view_name: order_items 15 | 16 | join: order_facts { 17 | view_label: "Orders" 18 | relationship: many_to_one 19 | sql_on: ${order_facts.order_id} = ${order_items.order_id} ;; 20 | } 21 | 22 | join: inventory_items { 23 | #Left Join only brings in items that have been sold as order_item 24 | type: full_outer 25 | relationship: one_to_one 26 | sql_on: ${inventory_items.id} = ${order_items.inventory_item_id} ;; 27 | } 28 | 29 | join: users { 30 | relationship: many_to_one 31 | sql_on: ${order_items.user_id} = ${users.id} ;; 32 | } 33 | 34 | join: user_order_facts { 35 | view_label: "Users" 36 | relationship: many_to_one 37 | sql_on: ${user_order_facts.user_id} = ${order_items.user_id} ;; 38 | } 39 | 40 | join: products { 41 | relationship: many_to_one 42 | sql_on: ${products.id} = ${inventory_items.product_id} ;; 43 | } 44 | 45 | join: repeat_purchase_facts { 46 | relationship: many_to_one 47 | type: full_outer 48 | sql_on: ${order_items.order_id} = ${repeat_purchase_facts.order_id} ;; 49 | } 50 | 51 | join: distribution_centers { 52 | type: left_outer 53 | sql_on: ${distribution_centers.id} = ${inventory_items.product_distribution_center_id} ;; 54 | relationship: many_to_one 55 | } 56 | } 57 | 58 | 59 | ######### Event Data Explores ######### 60 | 61 | explore: events { 62 | label: "(2) Web Event Data" 63 | 64 | join: sessions { 65 | sql_on: ${events.session_id} = ${sessions.session_id} ;; 66 | relationship: many_to_one 67 | } 68 | 69 | join: session_landing_page { 70 | from: events 71 | sql_on: ${sessions.landing_event_id} = ${session_landing_page.event_id} ;; 72 | fields: [simple_page_info*] 73 | relationship: one_to_one 74 | } 75 | 76 | join: session_bounce_page { 77 | from: events 78 | sql_on: ${sessions.bounce_event_id} = ${session_bounce_page.event_id} ;; 79 | fields: [simple_page_info*] 80 | relationship: many_to_one 81 | } 82 | 83 | join: product_viewed { 84 | from: products 85 | sql_on: ${events.viewed_product_id} = ${product_viewed.id} ;; 86 | relationship: many_to_one 87 | } 88 | 89 | join: users { 90 | sql_on: ${sessions.session_user_id} = ${users.id} ;; 91 | relationship: many_to_one 92 | } 93 | 94 | join: user_order_facts { 95 | sql_on: ${users.id} = ${user_order_facts.user_id} ;; 96 | relationship: one_to_one 97 | view_label: "Users" 98 | } 99 | } 100 | 101 | explore: sessions { 102 | label: "(3) Web Session Data" 103 | 104 | join: events { 105 | sql_on: ${sessions.session_id} = ${events.session_id} ;; 106 | relationship: one_to_many 107 | } 108 | 109 | join: product_viewed { 110 | from: products 111 | sql_on: ${events.viewed_product_id} = ${product_viewed.id} ;; 112 | relationship: many_to_one 113 | } 114 | 115 | join: session_landing_page { 116 | from: events 117 | sql_on: ${sessions.landing_event_id} = ${session_landing_page.event_id} ;; 118 | fields: [session_landing_page.simple_page_info*] 119 | relationship: one_to_one 120 | } 121 | 122 | join: session_bounce_page { 123 | from: events 124 | sql_on: ${sessions.bounce_event_id} = ${session_bounce_page.event_id} ;; 125 | fields: [session_bounce_page.simple_page_info*] 126 | relationship: one_to_one 127 | } 128 | 129 | join: users { 130 | relationship: many_to_one 131 | sql_on: ${users.id} = ${sessions.session_user_id} ;; 132 | } 133 | 134 | join: user_order_facts { 135 | relationship: many_to_one 136 | sql_on: ${user_order_facts.user_id} = ${users.id} ;; 137 | view_label: "Users" 138 | } 139 | } 140 | 141 | 142 | ######### Advanced Extensions ######### 143 | 144 | explore: affinity { 145 | label: "(4) Affinity Analysis" 146 | 147 | always_filter: { 148 | filters: { 149 | field: affinity.product_b_id 150 | value: "-NULL" 151 | } 152 | } 153 | 154 | join: product_a { 155 | from: products 156 | view_label: "Product A Details" 157 | relationship: many_to_one 158 | sql_on: ${affinity.product_a_id} = ${product_a.id} ;; 159 | } 160 | 161 | join: product_b { 162 | from: products 163 | view_label: "Product B Details" 164 | relationship: many_to_one 165 | sql_on: ${affinity.product_b_id} = ${product_b.id} ;; 166 | } 167 | } 168 | 169 | explore: orders_with_share_of_wallet_application { 170 | label: "(5) Share of Wallet Analysis" 171 | extends: [order_items] 172 | view_name: order_items 173 | 174 | join: order_items_share_of_wallet { 175 | view_label: "Share of Wallet" 176 | } 177 | } 178 | 179 | explore: journey_mapping { 180 | label: "(6) Customer Journey Mapping" 181 | extends: [order_items] 182 | view_name: order_items 183 | 184 | join: repeat_purchase_facts { 185 | relationship: many_to_one 186 | sql_on: ${repeat_purchase_facts.next_order_id} = ${order_items.order_id} ;; 187 | type: left_outer 188 | } 189 | 190 | join: next_order_items { 191 | from: order_items 192 | sql_on: ${repeat_purchase_facts.next_order_id} = ${next_order_items.order_id} ;; 193 | relationship: many_to_many 194 | } 195 | 196 | join: next_order_inventory_items { 197 | from: inventory_items 198 | relationship: many_to_one 199 | sql_on: ${next_order_items.inventory_item_id} = ${next_order_inventory_items.id} ;; 200 | } 201 | 202 | join: next_order_products { 203 | from: products 204 | relationship: many_to_one 205 | sql_on: ${next_order_inventory_items.product_id} = ${next_order_products.id} ;; 206 | } 207 | } 208 | 209 | 210 | explore: inventory_items{ 211 | label: "(7) Stock Analysis" 212 | fields: [ALL_FIELDS*,-order_items.median_sale_price] 213 | 214 | join: order_facts { 215 | view_label: "Orders" 216 | relationship: many_to_one 217 | sql_on: ${order_facts.order_id} = ${order_items.order_id} ;; 218 | } 219 | 220 | join: order_items { 221 | #Left Join only brings in items that have been sold as order_item 222 | type: left_outer 223 | relationship: many_to_one 224 | sql_on: ${inventory_items.id} = ${order_items.inventory_item_id} ;; 225 | } 226 | 227 | join: users { 228 | relationship: many_to_one 229 | sql_on: ${order_items.user_id} = ${users.id} ;; 230 | } 231 | 232 | join: user_order_facts { 233 | view_label: "Users" 234 | relationship: many_to_one 235 | sql_on: ${user_order_facts.user_id} = ${order_items.user_id} ;; 236 | } 237 | 238 | join: products { 239 | relationship: many_to_one 240 | sql_on: ${products.id} = ${inventory_items.product_id} ;; 241 | } 242 | 243 | join: repeat_purchase_facts { 244 | relationship: many_to_one 245 | type: full_outer 246 | sql_on: ${order_items.order_id} = ${repeat_purchase_facts.order_id} ;; 247 | } 248 | 249 | join: distribution_centers { 250 | type: left_outer 251 | sql_on: ${distribution_centers.id} = ${inventory_items.product_distribution_center_id} ;; 252 | relationship: many_to_one 253 | } 254 | } 255 | 256 | explore: inventory_snapshot { 257 | label: "(8) Historical Stock Snapshot Analysis" 258 | join: trailing_sales_snapshot { 259 | sql_on: ${inventory_snapshot.product_id}=${trailing_sales_snapshot.product_id} 260 | AND ${inventory_snapshot.snapshot_date}=${trailing_sales_snapshot.snapshot_date};; 261 | type: left_outer 262 | relationship: one_to_one 263 | } 264 | 265 | join: products { 266 | sql_on: ${inventory_snapshot.product_id} = ${products.id} ;; 267 | relationship: many_to_one 268 | } 269 | 270 | join: distribution_centers { 271 | sql_on: ${products.distribution_center_id}=${distribution_centers.id} ;; 272 | relationship: many_to_one 273 | } 274 | } 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | explore: kitten_order_items { 298 | label: "Order Items (Kittens)" 299 | hidden: yes 300 | extends: [order_items] 301 | 302 | join: users { 303 | view_label: "Kittens" 304 | from: kitten_users 305 | } 306 | } 307 | --------------------------------------------------------------------------------