cube icon indicating copy to clipboard operation
cube copied to clipboard

Allow for `meta` to be defined for each dimension at the `view` level

Open marxlow opened this issue 9 months ago • 11 comments

Is your feature request related to a problem? Please describe.

  • We are using Views to hide the implementation details of the underlying Cube. --> Our Frontend will only interface with Views.
  • We have a need to use the dimension's meta property to dynamically change the behaviour on the Frontend. e.g meta: { isRecommended: true }.
  • The only way to get this to work is to add this meta meta: { isRecommended: true } to the underlying Cube. But that creates a strong leakage & coupling between the Cube & the View.
  • This will be an issue if there are multiple Views using the same underlying cube e.g:
view ABC {
  cubeA { 
    meta: { showABC: true }
  }
}

view DEF {
  cubeA { 
    meta: { showABC: true } // <-- Unnecessary meta leaked from view ABC's implementation detail
  }
}

Describe the solution you'd like Allow views to overwrite the meta property of the underlying cube:

View A {
  cubes: [
    join_path: () => 'some_cube',
    alias: `candidate`,
    prefix: true,
    includes: [
      {
        name: `someDimension`,
        meta: {
          someFlagSpecificToViewA: true, // <-- Net new property
        },
      },
    ],
  },
 ]

Describe alternatives you've considered Adding the meta in Cube works but as mentioned earlier creates coupling & leakage

Additional context From a quick scan of the codebase this looks logic can be implemented in the CubeSymbols.js --> membersFromCubes function here (https://github.com/cube-js/cube/blob/master/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js)

marxlow avatar Nov 09 '23 07:11 marxlow

Hey @marxlow 👋 This is an interesting feature request—now I'm trying to understand whether it aligns with the fundamental premise of views that they don't define things (e.g., members) themselves but only expose members of cubes.

Your use case makes sense to me, but I'm currently not sure if there are more use cases that would benefit from such meta extension logic. Could you please elaborate on what flags like someFlagSpecificToViewA might be used for?

Also, I wonder if this https://github.com/cube-js/cube/pull/7327, once merged, would be a working solution for your use case.

igorlukanin avatar Nov 13 '23 10:11 igorlukanin

Hey @igor! I think it aligns, Views are not defining the members themselves but will be the layer in Cube that holds business/product logic. Using meta at the cube layer is too low level and leaks metadata to other views that do not require the data. Let me elaborate more with an example :)

Let's say I am building out an Analytics tool dealing with demographic data using 2 Charts.

Chart A --> Strictly only allows users to filter by Gender Chart B --> Strictly only allows users to filter by City

Currently we have to "leak" what I consider to be business/product logic from each Charts A & B down to the Cube level. Because we cannot override dimension meta values at the view level. Our Cubes may look like this:

cube('people', () => {
  dimensions: {
    gender: { 
      meta: {
        useInChartA: true  // --> Notice the naming has to be specific to the view's implementation
    },
    city: { 
      meta: {
        useInChartB: true // --> Notice the naming has to be specific to the view's implementation
    },
  }
});

View('A', () => cubes: ['people'])
View('B', () => cubes: ['people'])

HOWEVER, If we had the ability to override dimension meta values. This is what the implementation could've looked like instead. Cubes could've stayed agnostic to the implementation details of the view and we get a clearer separation of concerns.

cube('people', () => {
  dimensions: {
    gender: { }
    city: {  }
});

View('A', () => cubes: [
  {
    join_path: () => 'people' ,
    includes: [{ gender: meta: { useAsFilter: true }}]  // --> Notice the naming is now common
  }

View('B', () => cubes: [
  {
    join_path: () => 'people' ,
    includes: [{ city: meta: { useAsFilter: true }}]
  }

In the second approach we see that logic is encapsulated within each view. The Chart consuming from each View can then due with a single useAsFilter flag rather than writing 2 separate logic to due with useInChartA/B.


Yeap I saw that :) it #7327 can work too, but it's less clean IMO. We have to tie the dimension of each Cube level back up to the meta defined at the View level which isn't as cohesive. e.g:

// Using #7327 - meta at view level
View(A, () => {
   meta: { dimensionA: useAsFilter }
});

// Using the feature suggestion - meta override at the dimension level
View('A', () => cubes: [
  {
    join_path: () => 'people' ,
    includes: [{ gender: meta: { useAsFilter: true }}]
  }

marxlow avatar Nov 13 '23 11:11 marxlow

Hey @igor, would you accept a PR to add this feature? If we came up with the PR, what's the process and timeline to get this merged/released?

marxlow avatar Feb 26 '24 14:02 marxlow

Hi @igorlukanin, what do you think, if this was PRed would it be accepted?

NatElkins avatar Mar 05 '24 20:03 NatElkins

@igorlukanin Thought I'd give you a bump on this. Are you open to accepting a PR on this?

NatElkins avatar Mar 19 '24 15:03 NatElkins

In my opinion, it looks like the ability to define top-level meta in views pretty much solves the issue and provides the way forward here.

View members would inherit their meta payload from cubes—and you also have an option to enrich that data with top-level meta in each view. In a way, it looks logical: if you go down the "one-view-per-chart" path, it makes sense to have view-specific (chart-specific) metadata.

IMO, the proposed change looks like some syntactic sugar rather than something that unblocks the use case—and we can probably collect more usage data before introducing more complexity in the implementation.

igorlukanin avatar Mar 20 '24 14:03 igorlukanin

I'd love to hear what @paveltiunov thinks as well.

igorlukanin avatar Mar 20 '24 14:03 igorlukanin

Hey @igorlukanin , in this case we need the behaviours (defined using meta) at the dimension level to be different for each View and . meta at the Views level is not at the right layer. Let me try to make it clearer to see if it helps :)

// Given 2 views
view ABC {
  cubeA { 
    includes: [ 
      {
          name: 'some_dimension_with_different_behaviours_in_view' , // <-- Unable to define a dimension level meta
      }
    ] 
  }
}

view ABC {
  cubeA { 
    includes: [ 
      {
          name: 'some_dimension_with_different_behaviours_in_view' ,  // <-- Unable to define a dimension level meta
      }
    ] 
  }
}

cubeA {
   dimensions: [
    ...
       some_dimension_with_different_behaviours_in_view: {
         meta: { } // --> Cannot be overwritten by Views. Sha
       }
    ...
   ]
}

marxlow avatar Mar 20 '24 20:03 marxlow

@paveltiunov Any views on this (no pun intended 😉 )?

NatElkins avatar Apr 02 '24 19:04 NatElkins

@marxlow @NatElkins Let me expand the pseudo code example above:

// Given 2 views
view View1 {
  cubeA { 
    includes: [ 
      {
          name: 'some_dimension_with_different_behaviours_in_view' , // <-- Unable to define a dimension level meta
      }
    ] 
  },

  meta: {
    cubeA: {
      some_dimension_with_different_behaviours_in_view: {
        // metadata that details that behavior for View1
      }
    }
  }
}

view View2 {
  cubeA { 
    includes: [ 
      {
          name: 'some_dimension_with_different_behaviours_in_view' ,  // <-- Unable to define a dimension level meta
      }
    ] 
  },

  meta: {
    cubeA: {
      some_dimension_with_different_behaviours_in_view: {
        // metadata that details that behavior for View2
      }
    }
  }
}

cubeA {
   dimensions: [
    ...
       some_dimension_with_different_behaviours_in_view: {
         meta: { } // --> Cannot be overwritten by Views. Sha
       }
    ...
   ]
}

Would top-level meta in View1 and View2 work for your use case? I can totally see providing details about included dimensions this way.

igorlukanin avatar Apr 03 '24 14:04 igorlukanin

@igorlukanin Thanks, that was actually very helpful. I can't speak for @marxlow but I suppose that will work for us actually. It's not quite as "clean," in the sense that now we have data stored in two places and we'll need to check every dimension that we're getting from the cube against the metadata at the view level. But it should be relatively scalable, so long as we don't make any mistakes in the implementation 😃

NatElkins avatar Apr 16 '24 13:04 NatElkins