Code Reusability: Extending Explores and Views (3.24+)

Release 3.24 will come with the ability to extend Explores and Views. These are key features in building reusable models.

Building extensions is a pretty advanced topic. In order to understand how this works, you will first need to understand LookML’s facilities for naming explores, views and fields. Take a moment to review this, even if you are familiar with it. The subtleties are important.

LookML is implemented as an array of hashes in YAML. Each LookML Object (explore, view, field, join, dashboard, or dashboard element) is a named hash. LookML’s extensions are a mechanism for deeply copying and merging these objects.

Extending Explores and Views in an Existing Model

A design goal for extensions is that you can put all your extensions into a new separate file so that if the original model changes, you get all of the changes. Additionally, all the logic surrounding the extension is on one place. For example, a rolling average extension includes the explore, view and field definitions as well as dashboard that displays rolling averages. Simply drop the file into your model and change the name of the explore being extended.

Example

Assume the base e-commerce model:

Notice that the view: parameter is set in the explore. The view to use in an explore is by default the same name as the explore. Setting the view: allows the explore to be extended without having to set this parameter (this will likely be automatically set by the generator in the future).

- explore: orders
  view: orders
  joins:
    - join: users
      foreign_key: orders.user_id

    - join: order_items
      sql_on: ${order_items.order_id} = ${orders.id}
      relationship: one_to_many
      
    - join: inventory_items
      foreign_key: order_items.inventory_item_id

    - join: products
      foreign_key: inventory_items.product_id

Extending a joined view within an Explore.

Suppose the original model doesn’t have much in the way of age analysis. We can extend the model by simply creating a file with our extensions.

Extending a view within an explore happens in two steps:

  1. Create a new view that has the desired fields to extend the original view. In this case, we extend users.
  2. Create a new explore that extends orders, change the view for users by declaring the from: parameter. Notice that the linkage for users was copied from the join in the original orders view.
- explore: orders_with_age_extensions
  extends: orders
  joins:
  - join: users
    from: users_with_age_extensions
    
    
- view: users_with_age_extensions
  extends: users
  fields:
  - measure: average_age
    type: average
    sql: ${age}
    
  - dimension: age_tiers
    type: tier
    style: integer
    tiers: [10,20,30,50,70]
    sql: ${age}
  sets:
    detail: [SUPER*, age]

Example Extending just the Base View

There are 4 names associated with an explore. Its name in the UI (label:), its name in the url (explore:), the view that it is built from (from:), and the alias to use in the sql query and to prefix fields with (view:). It is important to understand each of these use cases. When extending a view, you usually want to change most of these.

- explore: better_orders
  extends: orders
  view: orders              
  from: orders_extended

- view: orders_extended
  extends: orders
  fields:
  - measure: count_in_california
    type: count
    filters:
      users.state: California

Play with it.

There is additional information about how this will work on learnbeta. Chat with a looker support analyst if you would like to play and don’t have an account.

Comment on it.

We’d love to hear your feedback. Let us know how you’re using the feature, what else you’d like to do with it, or anything else you think we should know.

4 Likes

Also cool in the example is how you used extensions with a table of dates to implement a date dimension pattern. We do this a lot in derived tables, this is much cleaner.

I think this definitely helps in creating reusable models and code. Will need to play with it more to see how it fits into our process, however a couple cases that I see are:

  • Having clients with similar datasets. All clients may have data A but some may have A+BC but others A+XY. In the end they will have all the same model but extended or fortified with other sources.
  • Extending or separating measures such as the age example above. It would be nice (though not necessary) to keep the base measures like counts separate from the more robust or custom measures such as moving averages, etc. For an analyst, this may be great, allowing them to extend and prototype without actually altering the production code (though a developer without deploy rights I think is identical).

This leads to an issue or two that I foresee:

  1. If I want to extend the view A with XY it will need to be named differently (hence having A and also A+BC). I can see the reasoning for this however I may simply wish all my clients to simply see A however depending on the client it actually may be A+BC or A+XY. I did verify that hiding A actually hides all extensions to it also; if a bug, this may help.
  2. To extend a view seams overly complicated for most cases. If A has view v which needs a new calculation, I would like to simply extend v to v’ and explore A as usual not needing to create and use A’.

Still great stuff and I am sure I will use it. Just my initial two cents after playing with this for a 30 minutes.

1 Like

This is so great. Lots of potential to really DRY out the LookML codebase here. Question: how does over-riding definitions work?

The few use cases I’ve tried it seems to work mostly as expected, but I’d love to see some more documentation on what’s happening here.

Edit: I’d be happy to talk about feature requests/use cases with anyone on the Looker team is interested.

Hey Michael!

Extends works with a ‘copy and merge’ approach. So imagine I have these two objects:

- explore: foo
  persist_for: '12 hours'
  extends: bar

- explore: bar
  persist_for: '5 minutes'

First, Looker will combine all the YAML properties of foo and bar. Then, in the case of any conflicts, foo will win. In other words, we give precedence to anything explicitly defined in the object which is inheriting code from somewhere else. In the example above, foo will have a 12 hour cache.

We do the same thing for all child objects of foo and bar. Let’s look at our example again:

- explore: foo
  extends: bar
  joins: 
  - join: baz
    sql_on: ${bar.baz_id} = ${baz.id} AND ${foo.type_id} = 'purchase'

- explore: bar
  joins: 
  - join: baz
    sql_on: ${bar.baz_id} = ${baz.id}

  - join: qux
    sql_on: ${bar.qux_id} = ${qux.id}

In this case, foo will inherit the join: qux directly from bar, but will override join: baz logic with the more explicit sql_on parameter declared in the explore: foo.

Hope this helps!

2 Likes

Excellent. Exactly what I was looking for.

It would be really great to be able to have a syntax shortcut to just reuse certain fields across the model (vs the whole explore/view). For example, storing a two letter country code for every event across many different tables means we have to copy and paste the dimension below into every view (and this can be a pain to change if, for example, Djibouti falls out of the top five!).

  - dimension: country_full_name
    label: "Country (Top 5 + ROW)"
    sql_case:
      Djibouti: ${TABLE}.country = 'DJ'
      Zimbabwe: ${TABLE}.country = 'ZW'
      Germany: ${TABLE}.country = 'DE'
      Ireland: ${TABLE}.country = 'IE'
      India: ${TABLE}.country = 'IN'
      else: Rest of World

I think there’s a sneaky way to do this with joins and scoping but it would be really nice to have a syntax shortcut like dimension: common.country_full_name. Maybe there’s a way to do this that I’m missing?

I think you can do this with extends. If you put your dimension definition in it’s own view (I prefix all of my reusable views with BASE_), then you just put extends: BASE_top_country for every view you want to use this in. At least, this has been working for my use cases…

5 Likes

Yep, my use case is totally doable with extends. Just needed someone to give me an example, thanks :ghost:

We love using this feature. We have a usage log that is split up in to daily/weekly/monthly rollups that have the same structure, and can now manage one extends view to make changes to all 3.

It’s been super helpful in keeping our models standardized and clean.

2 Likes

I finally refactored our model file to use this functionality.

Question: we have a “users” view with dozens of views joined off of it (e.g. “user_relationship_facts”). Is there a way for us to do something like:

- explore: orders
  joins:
  - join: primary_users
    from: users_with_joins
    foreign_key: orders.user1

  - join: secondary_users
    from: users_with_joins
    foreign_key: orders.user2

Then I could access fields like “PRIMARY USERS USERS RELATIONSHIP FACTS Number of Orders Placed in Past 30 Days”

@Daniel_Demetri, Unfortunately, there isn’t a way to use extends polymorphically. I totally get why you would want it (and I really want it too), still thinking about how to do it without making it very complex.

I’ve thought about some kind of templating system, when you extend something, you give values for the templates, but very quickly things get very complex.

Gotcha. Thanks for the quick reply. Is it actually complicated? What if we make it possible to join an explore and then all “joined joins” are named “{joined explore name} {join name}”. Eg “PRIMARY USERS USER RELATIONSHIP FACTS”

1 Like

It would be nice to be able to extend dashboard elements. For example, say a dashboard should have multiple elements reflecting time spans, an extension could look like the following. (The names in this LookML fragment are not actual names from my company’s model, and pardon me if these exact dashboard elements wouldn’t make sense in real life.)

- dashboard: last_purchase
  title: "Customer's last purchase"
  layout: tile
  tile_size: 60
  refresh: 24 hours

  filters:
  - name: customer_class
    title: Customer class
    type: field_filter
    explore: cust
    field: cust.class
    default_value: 'corporate'

  elements:
  - name: foo
    extension: required # This would be a new parameter for a dashboard element.
    type: table  # This parameter, and the following ones, are inherited by the extensions.
    total: true
    font_size: small
    refresh: 24 hours
    model: production
    explore: cust
    listen:
      customer_class: cust.class
    dimensions: cust.class
    measures: [cust_sales.count]
    
  - name: last
    extends: foo
    title: 'Last date a customer purchased'
    measures: cust.last_sale
  - name: ten
    extends: foo
    title: 'Sales in the last 10 days'
    filters:
      cust.last_sale: '240 hours'
  - name: thirty
    extends: foo
    title: 'Sales in the last 30 days, before the last 10'
    filters:
      cust.last_sale: '720 hours ago for 480 hours'
  - name: ninety
    extends: foo
    title: 'Sales in the last 90 days, before the last 30'
    filters:
      cust.last_sale: '2160 hours ago for 1440 hours'
  - name: preninety
    title: 'Sales longer ago than 90 days'
    extends: foo
    filters:
      cust.last_sale: 'before 2160 hours ago'
  - name: all
    extends: foo
    title: 'ALL customers'
1 Like

Michael,

That’s a really interesting idea! I imagine that this would save a lot of copying and pasting for tile settings. I’ll definitely pass the idea along. For what it’s worth, it is possible to extend dashboards themselves, which isn’t mentioned in this article. However, I can see why tile extensions would have a different use.

1 Like

Would love to see Dashboard Element Extends!

I’ll definitely relay this to our product team, thanks for the feedback @Jesse_St_Charles !

1 Like

@Morgan, @justin1, has this been made a feature request? I poked around in that section and don’t see it - would love to put my support behind it.

I’d take this a step further than the example provided by @Michael, and argue that extended elements should be reusable not only on the same dashboard, but across multiple dashboards (if Dashboard2 extends Dashboard1).

Today - developing with user defined dashboards - you can save a look, and you can plop that look on more than one dashboard. When you edit that look, those edits cascade down to all of the dashboards where that look has been embedded. There is no similar experience when developing in LookML; if you want the same element on more than one dashboard, you’re required to copy and paste the same chunk of code into multiple dashboards (and trying to remember where they are if you want to maintain consistent changes in the future).

What would really be powerful is if the most important chart elements (aka looks) could be defined in LookML one time, and then if they needed to be repeated on multiple dashboards - either in the standard format or with some modification - we could make use of the extends feature.

Maybe I’m already stating the obvious known goal and you’re just working on implementation, but it would be great to hear if that’s the case!

Hi @TimothyBurke,

Thank you for the insightful feedback. We love hearing back ideas like this that can make Looker even more powerful and efficient. It looks like they are still working on how best to tackle this challenge while maintaining the integrity. I can loop in my Product team about your use case as well and make them aware of your interest.

Cheers,
Vincent