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

[Lens] Register saved object references #74523

Merged

Conversation

flash1293
Copy link
Contributor

@flash1293 flash1293 commented Aug 6, 2020

Fixes #55906

This PR makes sure the Lens saved object does not include any direct references to other saved objects.

This changes a lot of things, I tried to list them out here:

Datasource API

We already have a separation of persistable state and runtime state - this can be used for extracting references. This PR extends the API by returning saved object references together with the serialized state. This means getPersistableState is responsible for pulling out all references from the state to turn it persistable. The initialize function is handling the other way around and is injecting references into the runtime state: https://github.com/elastic/kibana/pull/74523/files#diff-c9e959476594f76ae1ce129e8beb7e50R148

Visualization API

We didn't use the differentation of runtime and persistable state for visualizations - in fact, we passed in the wrong shape in some places. This PR just removes the persistable visualization state and the getPersistableState function. initialize is still needed to call side effects.

The toExpression function didn't use the whole frame api, but just the datasource meta information. To make it easier to construct the necessary parts when creating the expression in the embeddable, this PR removes the frame public api and just passses in the datasource layers https://github.com/elastic/kibana/pull/74523/files#diff-c9e959476594f76ae1ce129e8beb7e50R502

Datasource meta data

There is an API for the datasource to return a list of related index patterns. This API returned the id and the title of the index patterns. As we don't needs this API anymore, this PR removes it and simply uses the references created by the datasource instead.

Frame injection/extraction

Extraction of references happens in getSavedObjectFormat: https://github.com/elastic/kibana/pull/74523/files#diff-c4eb3c94dab15e7c5a0438f644a0146b

Injection of references happens in app.tsx for filters and, because it's passed to the filter bar from there: https://github.com/elastic/kibana/pull/74523/files#diff-e074336b6e49a778a50e4f9ba090155aR238

Saved object changes

The saved object has to change to not include hard coded index pattern ids, only references.
The following places currently store ids:

  • Current index pattern id in datasource (indexpattern-datasource-current-indexpattern)
  • Per layer index pattern id in datasource (indexpattern-datasource-layer-${layerid})
  • Per stored filter index pattern id (filter-index-pattern-${index})

In the saved object attributes, these properties can simply be stripped out, because they are always required.

For filters, a indexRefName property is added to the filter definition (this logic was copied from how the search source is handling this): https://github.com/elastic/kibana/pull/74523/files#diff-41572ea9f9059d9f9e4bb899b96c8f51R27

Removing expression

The expression is currently stored in the saved object and used directly from there in the embeddable. This PR removes the expression from the saved object and builds it on the fly when the embeddable is created using a similar logic in the embeddable factory. This requires changes in a few places:

Migrating

The existing saved objects need to get migrated. To do so, I copied over the necessary extraction logic into a migration script. It's not using the same code because it would require to move this code into the common folder which would mean quite some refactoring and it would make it easy to change migration code which should be avoided if possible.
https://github.com/elastic/kibana/pull/74523/files#diff-1dad129f516cb546effd934f4ce57a22

The migration also removes the expression from saved objects.

@flash1293 flash1293 added Feature:Lens v7.10.0 v8.0.0 release_note:skip Skip the PR/issue when compiling release notes labels Aug 14, 2020
@flash1293
Copy link
Contributor Author

Jenkins, test this.

@flash1293 flash1293 marked this pull request as ready for review August 17, 2020 12:00
@flash1293 flash1293 requested a review from a team August 17, 2020 12:00
@flash1293 flash1293 added the Team:Visualizations Visualization editors, elastic-charts and infrastructure label Aug 17, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-app (Team:KibanaApp)

Copy link
Contributor

@wylieconlon wylieconlon left a comment

Choose a reason for hiding this comment

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

I tested this out and it definitely was working, but I wasn't happy with the scope of the changes to the embeddable render. I prefer keeping the embeddable as close to output-only as we can. I've started a PR on top of yours which works in 7.10, but does not have migrations or unit tests- let's discuss if that's a better approach.

@@ -107,7 +111,6 @@ export function EditorFrame(props: EditorFrameProps) {
datasourceLayers[layer] = props.datasourceMap[id].getPublicAPI({
state: datasourceState,
layerId: layer,
dateRange: props.dateRange,
Copy link
Contributor

Choose a reason for hiding this comment

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

This change looks unrelated, but I'm fine with the cleanup of the unused dateRange here.

)
: props.doc.state.visualization,
},
},
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Good change to actually pass the saved document through the initialization

Comment on lines 118 to 123
framePublicAPI: {
datasourceLayers: Record<string, DatasourcePublicAPI>;
query: Query;
dateRange?: DateRange;
filters: Filter[];
};
Copy link
Contributor

Choose a reason for hiding this comment

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

This change doesn't seem to be required for the PR

Suggested change
framePublicAPI: {
datasourceLayers: Record<string, DatasourcePublicAPI>;
query: Query;
dateRange?: DateRange;
filters: Filter[];
};
framePublicAPI: FramePublicAPI;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should have expanded on that in the description - this change is necessary because when building the expression in the embeddable factory, we don't have a meaningful date range available. This change (together with the very first one you commented on) is relaxing the requirement to provide a date range in those places.

const visualizationExpression = visualization.toExpression(
visualizationState,
framePublicAPI.datasourceLayers
);
Copy link
Contributor

Choose a reason for hiding this comment

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

I was initially skeptical of this change, but I am now on board.

onChange: ({ filterableIndexPatterns, doc, isSaveable }) => {
if (isSaveable !== state.isSaveable) {
setState((s) => ({ ...s, isSaveable }));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: This looks like it could trigger a double-render in some cases, if the change is something like clearing a layer. I don't think this is a problem, but wanted to mention it.

});

const doc = getSavedObjectFormat({
const { filterableIndexPatterns, doc, isSaveable } = getSavedObjectFormat({
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const { filterableIndexPatterns, doc, isSaveable } = getSavedObjectFormat({
props.onChange(getSavedObjectFormat({

import { getEditPath } from '../../../common';
import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public';
import { buildExpression } from '../editor_frame/expression_helpers';
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is the change that has surprised me the most, because it means that we are no longer using the same expressions in the editor and embeddable.

I've found a way to make the expression always the same in the editor and the embeddable by using variables. Please take a look at my PR on your fork!

Copy link
Contributor Author

@flash1293 flash1293 Aug 19, 2020

Choose a reason for hiding this comment

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

Thanks for reviewing so thoroughly on both code and architectural level. I realize I never wrote down the reasons to remove the expression, so this is definitely on me.

There are two reasons why I think removing the expression completely for now is the right thing to do:

  • As the state is redundant with the expression, it's always a source of potential errors in migrations - we basically need to re-implement a small part of the expression building logic in respective migrations and hope it matches what the actual expression building does. In fact, it's what often makes migrations necessary in the first place because changes are limited to the expression. By not saving the expression, this source of bugs (and code/complexity in general) can be removed. We originally decided to save the expression to avoid initializing the datasources in the embeddable, but this is a purely theoretical problem, as we only need access to index patterns which will be cached already anyway.
  • Specifically, the app arch team plans to change the parameter structure of the esaggs function from a JSON string to nested sub expressions. When regenerating the expression, this change is trivial (just a single line in the toExpression function). With saved expressions, it becomes much more complex, because the logic for generating the nested sub expression ast is not available at migration time.

If your concern is the different code generating the expression in editor vs. embeddable, I could refactor the shared logic into a stateless helper that's used in both places in the same way. Do you think that would work?

This approach was also discussed with the app arch team.

@flash1293
Copy link
Contributor Author

@elasticmachine merge upstream

@flash1293
Copy link
Contributor Author

flash1293 commented Aug 20, 2020

@wylieconlon Changed as discussed:

  • Move the expression building logic out of embeddable factory
  • Use same code to initialize datasources in embeddable factory and editor frame
  • Use expression execution search context instead of messing with expression in all places
  • Remove filterable index patterns and use the references instead

Let's postpone further cleanup work into separate PRs as this PR is already doing multiple things.

Copy link
Contributor

@wylieconlon wylieconlon left a comment

Choose a reason for hiding this comment

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

Found a bug in the autorefresh behavior, but overall looking good. Tested the migration from 7.9 -> 7.10 and it worked. Please don't merge until the autorefresh is fixed.

@@ -160,13 +161,36 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe
<ExpressionWrapper
ExpressionRenderer={this.expressionRenderer}
expression={this.expression}
context={this.currentContext}
searchContext={this.getMergedSearchContext()}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you're forgetting to pass in the autorefresh parameter, so the dashboard autorefresh is not happening. You'll need to add something like context={{ lastReloadRequestTime: this.externalSearchContext.lastReloadRequestTime }}

@@ -82,20 +88,67 @@ const setLastUsedIndexPatternId = (storage: IStorageWrapper, value: string) => {
writeToStorage(storage, 'indexPatternId', value);
};

const CURRENT_PATTERN_REFERENCE_NAME = 'indexpattern-datasource-current-indexpattern';
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I see the reasoning behind this- why do we add a reference to the selected index pattern, isn't this only part of the local state, not the persisted state?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, we are also persisting the current index pattern: https://github.com/elastic/kibana/blob/master/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx#L136

I think we can discuss whether it makes sense, but I would like to do that separately.

@flash1293
Copy link
Contributor Author

@wylieconlon Addressed most of your comments, but I think we don't need to pass lastReloadRequestTime down as it's just used in the embeddable itself. Could you take another look?

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

@kbn/optimizer bundle module count

id value diff baseline
lens 255 +2 253

async chunks size

id value diff baseline
lens 25.6KB +444.0B 25.2KB

page load bundle size

id value diff baseline
lens 866.9KB +13.2KB 853.7KB

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Copy link
Contributor

@mbondyra mbondyra left a comment

Choose a reason for hiding this comment

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

Tested the editor, dashboard and migrations from 7.9. Works as expected.

@flash1293 flash1293 merged commit 86f73cb into elastic:master Aug 21, 2020
flash1293 added a commit to flash1293/kibana that referenced this pull request Aug 21, 2020
gmmorris added a commit to gmmorris/kibana that referenced this pull request Aug 21, 2020
* master: (71 commits)
  [Lens] Show 'No data for this field' for empty field in accordion (elastic#73772)
  Skip failing lens test
  Configure ScopedHistory consistenty regardless of URL used to mount app (elastic#75074)
  Fix returned payload by "search" usage collector (elastic#75340)
  [Security Solution] Fix missing key error (elastic#75576)
  Upgrade EUI to v27.4.1 (elastic#75240)
  Update datasets UI copy to data streams (elastic#75618)
  [Lens] Register saved object references (elastic#74523)
  [DOCS] Update links to Beats documentation (elastic#70380)
  [Enterprise Search] Convert our `public_url` route to `config_data` and collect initialAppData (elastic#75616)
  [Usage Collection Schemas] Remove Legacy entries (elastic#75652)
  [Dashboard First] Lens Originating App Breadcrumb (elastic#75470)
  Improve login UI error message. (elastic#75642)
  [Security Solution] modify circular deps checker to output images of circular deps graphs (elastic#75579)
  [Data Telemetry] Add index pattern to identify "meow" attacks (elastic#75163)
  Migrate CSP usage collector to `kibana_usage_collection` plugin (elastic#75536)
  [Console] Get ES Config from core (elastic#75406)
  [Uptime] Add delay in telemetry test (elastic#75162)
  [Lens] Use index pattern service instead saved object client (elastic#74654)
  Embeddable input (elastic#73033)
  ...
ThomThomson added a commit to ThomThomson/kibana that referenced this pull request Aug 22, 2020
ThomThomson added a commit that referenced this pull request Aug 24, 2020
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
ThomThomson added a commit to ThomThomson/kibana that referenced this pull request Aug 24, 2020
…ic#75714)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Lens release_note:skip Skip the PR/issue when compiling release notes Team:Visualizations Visualization editors, elastic-charts and infrastructure v7.10.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Lens] Saved object should register reference to index patterns to keep exports/imports consistent
5 participants