-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Lens] Register saved object references #74523
Conversation
…d-object-references
…ed-object-references
…ed-object-references
…ed-object-references
Jenkins, test this. |
…ed-object-references
…ed-object-references
Pinging @elastic/kibana-app (Team:KibanaApp) |
There was a problem hiding this 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, |
There was a problem hiding this comment.
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, | ||
}, | ||
}, |
There was a problem hiding this comment.
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
framePublicAPI: { | ||
datasourceLayers: Record<string, DatasourcePublicAPI>; | ||
query: Query; | ||
dateRange?: DateRange; | ||
filters: Filter[]; | ||
}; |
There was a problem hiding this comment.
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
framePublicAPI: { | |
datasourceLayers: Record<string, DatasourcePublicAPI>; | |
query: Query; | |
dateRange?: DateRange; | |
filters: Filter[]; | |
}; | |
framePublicAPI: FramePublicAPI; |
There was a problem hiding this comment.
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 | ||
); |
There was a problem hiding this comment.
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 })); | ||
} |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'; |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 thetoExpression
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.
@elasticmachine merge upstream |
…ed-object-references
…ed-object-references
@wylieconlon Changed as discussed:
Let's postpone further cleanup work into separate PRs as this PR is already doing multiple things. |
There was a problem hiding this 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.
x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
Outdated
Show resolved
Hide resolved
x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts
Outdated
Show resolved
Hide resolved
x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
Outdated
Show resolved
Hide resolved
x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx
Outdated
Show resolved
Hide resolved
@@ -160,13 +161,36 @@ export class Embeddable extends AbstractEmbeddable<LensEmbeddableInput, LensEmbe | |||
<ExpressionWrapper | |||
ExpressionRenderer={this.expressionRenderer} | |||
expression={this.expression} | |||
context={this.currentContext} | |||
searchContext={this.getMergedSearchContext()} |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx
Show resolved
Hide resolved
…ed-object-references
…ed-object-references
@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? |
💚 Build SucceededBuild metrics@kbn/optimizer bundle module count
async chunks size
page load bundle size
History
To update your PR or re-run it, just comment with: |
There was a problem hiding this 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.
* 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) ...
…ic#75714) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
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. Theinitialize
function is handling the other way around and is injecting references into the runtime state: https://github.com/elastic/kibana/pull/74523/files#diff-c9e959476594f76ae1ce129e8beb7e50R148Visualization 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-c9e959476594f76ae1ce129e8beb7e50R502Datasource 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-c4eb3c94dab15e7c5a0438f644a0146bInjection 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-e074336b6e49a778a50e4f9ba090155aR238Saved object changes
The saved object has to change to not include hard coded index pattern ids, only references.
The following places currently store ids:
indexpattern-datasource-current-indexpattern
)indexpattern-datasource-layer-${layerid}
)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-41572ea9f9059d9f9e4bb899b96c8f51R27Removing 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:
buildExpression
helper doesn't work with the whole frame public api, but with some optional properties: https://github.com/elastic/kibana/pull/74523/files#diff-61a6e70d17b513ae29040c0bff3aaa38R115Migrating
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.