Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updating tagcloud interpreter func arguments #33773

Merged
merged 19 commits into from
Apr 4, 2019

Conversation

ppisljar
Copy link
Member

@ppisljar ppisljar commented Mar 25, 2019

Summary

updating tagcloud interpreter func arguments:

  • properties were extracted out of visConfig json to actual arguments

for example:
tagcloud visConfig='{"metric": { "accessor": 1 }, "bucket": { "accessor": 0, "format": { "type": "ip" }}, "scale":"linear", "orientation": "angled", "minFontSize": 12, "maxFontSize": 24 }'
becomes:
tagcloud metric={vis_dimension 1} bucket={vis_dimension 0 format='ip'} scale='linear' orientation='angled' minFontSize: 12, maxFontSize: 24

this one was pretty simple, but indicates what we want to do.

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

@elasticmachine
Copy link
Contributor

💔 Build Failed

@ppisljar ppisljar force-pushed the interpreter/semantics/tagcloud branch from ec3850c to ecef915 Compare March 26, 2019 11:01
@elasticmachine
Copy link
Contributor

💔 Build Failed

@ppisljar ppisljar force-pushed the interpreter/semantics/tagcloud branch from ecef915 to b47107d Compare March 26, 2019 11:41
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Member

@lukeelmers lukeelmers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I jumped the gun here (I realize this is still WIP), but figured I’d add a couple comments since I was looking at it anyway.

metric: [0],
segment: [1, 2]
metric: [{ accessor: 0 }],
segment: [{ accessor: 1 }]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably add all of the new params here so that the snapshots are testing everything

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'll add another test for that, as this actually hit a bug :)

bucketFormatParams: {
types: ['string'],
default: '"{}"',
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interpreter tagcloud function unit tests will probably need to be updated too

default: 18,
},
maxFontSize: {
types: ['number'],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we (and how) provide minimum and maximum for this property ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@w33ble ^^ whats the canvas way ?

Copy link
Contributor

@w33ble w33ble Mar 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much like options, the interpreter doesn't care. Check it in the function, if the wrong value is provided, just throw with a helpful message.

types: ['string', 'null'],
default: '"{}"',
scale: {
types: ['string'],
Copy link
Member Author

@ppisljar ppisljar Mar 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we provide allowed options here ? ('linear', 'log', 'sqrt')
we already have this check on the chart level

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@w33ble ^^ whats the canvas way ?

Copy link
Contributor

@w33ble w33ble Mar 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the options argument for the function definition, since the auto-complete dialog will show those values. Then in the function itself, just throw with a helpful message if the value is invalid. An example of this is alterColumn

@ppisljar ppisljar requested a review from w33ble March 27, 2019 10:04
@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

args.metric :
context.columns.find(c => c.id === args.metric);
if (metricAccessor === undefined) {
throw new Error('invalid column name');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user sees these errors, giving them a little more information would likely help them be successful. Right now they get the same message with an invalid metric or bucket, at the very least you should tell them which argument is causing the error. Telling them why it's failing (unable to find column foo, or no column at index X) would be ideal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also these all need to be translated

@w33ble
Copy link
Contributor

w33ble commented Mar 27, 2019

So, I suspect you're not going to want all the arguments you've added, otherwise you're going to end up with a ton of code duplication, and it's going to get really hard to use when you have more than a single metric and bucket.

Instead, I would recommend thinking about how you can compose functions and types to build what you need. As one example, and for lack of a better name, maybe a visConfig function and type, so you'd end up with something more like this:

tagcloud visConfig={visConfig metric=1 bucket=0 bucketFormat='ip'} 
  scale='linear' orientation='angled' minFontSize: 12, maxFontSize: 24

Where the visConfig function returns a type: 'visConfig' object. I realize this looks like it just adds more boilerplate, but it allows you to do all the conversion back into a visConfig object in that function, so you don't end up copying and pasting the same code all over the place. It also separates the tagcloud concerns (font sizing, scaling, orientation) from the vis data.

Using composition like that allows you to build visConfig objects in other functions as well, so when you have a more complex use case (tables come to mind), it might make sense to write a new function for that use case, but produce the same type: 'visConfig' object. Or even borrowing from the existing response handlers you have, a function for series and a function for slices... really anything you need.

Hypothetically, you could end up with stuff like this, as examples:

tagcloud visConfig={visConfig metric=1 bucket=0 bucketFormat='ip'} 
  scale='linear' orientation='angled' minFontSize: 12, maxFontSize: 24
metric visConfig={visConfig metric=0} 
  mode=percentage colors='Green to Red'

It might also make sense to go even further and create functions that can produce metric and bucket objects...

tagcloud 
  visConfig={visConfig metric={visMetric 1 format='number'} bucket={visBucket 0 format='ip'}} 
  scale='linear' orientation='angled' minFontSize: 12, maxFontSize: 24

Or even without the function nesting...

tagcloud metrics={visMetric 1 format='number'} buckets={visBucket 0 format='ip'} 
  scale='linear' orientation='angled' minFontSize: 12, maxFontSize: 24

Imagine metrics and buckets are multi-args here...

table 
  metrics={visMetric 0} metrics={visMetric date format=date} 
  buckets={visBucket ip format=ip} perPage=20

I'm not sure which option is the best, I know practically nothing about visConfig objects, so you'd have to experiment a bit to figure that out. But function composition seems like a better way to do this.

@lukeelmers
Copy link
Member

I'm not sure which option is the best, I know practically nothing about visConfig objects, so you'd have to experiment a bit to figure that out. But function composition seems like a better way to do this.

I like the idea of making this more composable, since some visTypes will certainly have a lot more args than others (e.g. tile/region maps will have a ton). But I also am not sure what the best solution is as I can see pros and cons to both approaches.

For example, visConfig objects are tricky because they largely differ for each visualization, which is why I understand @ppisljar's current implementation of putting the config together inside the function that's specific to the current vis.

On the other hand, we could benefit from some sort of uniformity across visConfigs to enforce things like keeping keynames consistent between vis types. Composition can help here, as could proper (shared) typings for the visConfig object.

The one item that is somewhat consistent across all vis types is dimensions/metrics/buckets, and they are also conceptually different from most other visConfig items (which are more like "options" for the vis that a user might configure in the UI, whereas the dimensions have to do with selecting the right underlying data and aren't something a user is really going to see).

So perhaps @w33ble's idea of visMetric/visBucket functions would be a good middle ground (...or visDimensions or whatever we call them).

Not trying to spin the conversation too far outside the scope of this PR; I'm mostly just thinking through this out loud. 😉 Still not sure what the right answer is.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@ppisljar
Copy link
Member Author

lets leave this PR for now and not merge it, i will work on another function in the meanwhile.

i'll take metric as its just slightly more complicated, has few options, but allows you to specify multiple metrics. Still only a single bucket and there is no split chart option. Also all of the options are 'global' not per metric.

lets see how that goes, and then i'll give histogram a try: multiple metrics, multiple buckets, split charts and configuration on per-series level (one for each metric-bucket combination) ...

it will be then easier to figure out the best syntax.

@ppisljar ppisljar force-pushed the interpreter/semantics/tagcloud branch from 6fe2bdb to 55cf736 Compare March 28, 2019 13:16
@elasticmachine
Copy link
Contributor

💔 Build Failed

…tics/tagcloud

# Conflicts:
#	src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts
@elasticmachine
Copy link
Contributor

💔 Build Failed

format: {
types: ['string'],
default: 'string'
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be good to add help text on format too

@lukeelmers
Copy link
Member

@ppisljar Nice, I think this approach strikes a good balance. vis_dimension feels like a very intuitive way to separate data from UI

fn: (context, args) => {
const accessor = Number.isInteger(args.accessor) ?
args.accessor :
context.columns.find(c => c.id === args.accessor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're making assumptions about the type of context you're working with here. You should be explicit about only accepting a datatable type, so the interpreter can report an incorrect type, and also do automatic conversions, instead of the function just throwing.

Adding this to the function definition would be all it takes.

context: {
  types: ['datatable'],
}

@@ -27,7 +27,12 @@ export const kibanaDatatable = () => ({
return {
type: 'kibana_datatable',
rows: context.rows,
columns: context.columns,
columns: context.columns.map(column => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this change part of this PR? And also, do you still need the code on line 26?

context.columns.forEach(c => c.id = c.name);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was broken before, kibana_datatable requires column.id

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but the code on line 26 appears to be attempting to do the same thing. Can that be removed?

@@ -9,14 +9,13 @@ Object {
"listenOnChange": true,
},
"visConfig": Object {
"bucket": undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this safe? And is it safe for metric: undefined as well? You don't check for a value of either property before putting it on the visConfig object in tag_cloud_fn.js, so that can end up as the output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be safe, but i will still remove this, better if its not set at all

minFontSize: args.minFontSize,
maxFontSize: args.maxFontSize,
showLabel: args.showLabel,
metric: args.metric,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

metric and bucket both lack a default value, so they can both end up being undefined, as noted in the snapshot. I wanted to point that out since I don't know how safe that behavior is.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

import { i18n } from '@kbn/i18n';

export const visDimension = () => ({
name: 'vis_dimension',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note on the function name, we've avoided using underscores and other characters in Canvas because it makes the expression much more confusing. It's true for arguments, but it's especially true for function names.

For example, if I know I need the "vis dimension" function, I could type it as visdimension or visDimension, or even ViSdImEnSiOn... it's all case-insensitive for that reason. That underscore breaks that convention, now I need to know your naming scheme.

Copy link
Contributor

@w33ble w33ble left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still think you can remove line 26 in kibana_datatable.js now, but I don't see any reason to block the PR over it. Everything else looks good enough.

I encourage you to consider renaming the vis dimension function, and to generally avoid underscores when naming functions and arguments.

Copy link
Member

@lukeelmers lukeelmers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM too. Thanks @w33ble for the thoughts on naming & autocomplete... hadn't considered that. Currently we're using underscores in most of our vis function names, so perhaps it's worth updating all of them at once, as this isn't the only place underscores have been used.

Still think you can remove line 26 in kibana_datatable.js now

I agree with this, looks like that line is unnecessary and should probably be deleted.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@ppisljar ppisljar force-pushed the interpreter/semantics/tagcloud branch from 63f6fa5 to 5815675 Compare April 4, 2019 11:45
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants