Skip to content

[legacy visualization] Register vis type with async callback#267598

Open
nreese wants to merge 18 commits into
elastic:mainfrom
nreese:async_vis_types
Open

[legacy visualization] Register vis type with async callback#267598
nreese wants to merge 18 commits into
elastic:mainfrom
nreese:async_vis_types

Conversation

@nreese
Copy link
Copy Markdown
Contributor

@nreese nreese commented May 4, 2026

PR updates visType registry:

  • read methods are async
  • Provide createBaseVisualizationAsync method to allow plugins to keep visType out of page load.

PR then updates vega and maps plugins to demonstrate async registry and verify page load reductions.

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 4, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 4, 2026

/ci

Comment on lines 53 to +57
createBaseVisualization: <TVisParams extends VisParams>(
config: VisTypeDefinition<TVisParams>
): void => {
const vis = new BaseVisType(config);
this.registerVisualization(vis);
this.registerVisualization(config.name, async () => new BaseVisType(config));
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Critical vis_types/types_service.ts:53

In the deprecated createBaseVisualization method, async () => new BaseVisType(config) constructs a BaseVisType instance that is then passed to registerVisualization, which constructs another BaseVisType around it—creating a double-wrapped object. The registerVisualization function expects a factory returning Promise<VisTypeDefinition>, but receives a factory returning Promise<BaseVisType>. This causes the inner BaseVisType to be treated as the raw config object, duplicating defaults and breaking type expectations.

       createBaseVisualization: <TVisParams extends VisParams>(
         config: VisTypeDefinition<TVisParams>
       ): void => {
-        this.registerVisualization(config.name, async () => new BaseVisType(config));
+        this.registerVisualization(config.name, async () => config);
       },
🤖 Copy this AI Prompt to have your agent fix this:
In file src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts around lines 53-57:

In the deprecated `createBaseVisualization` method, `async () => new BaseVisType(config)` constructs a `BaseVisType` instance that is then passed to `registerVisualization`, which constructs another `BaseVisType` around it—creating a double-wrapped object. The `registerVisualization` function expects a factory returning `Promise<VisTypeDefinition>`, but receives a factory returning `Promise<BaseVisType>`. This causes the inner `BaseVisType` to be treated as the raw config object, duplicating defaults and breaking type expectations.

Evidence trail:
- PR diff for types_service.ts: git_diff base=MERGE_BASE head=REVIEWED_COMMIT path=src/platform/plugins/shared/visualizations/public/vis_types/types_service.ts
- types_service.ts lines 27-37 (registerVisualization): creates `new BaseVisType(config)` from the factory result
- types_service.ts line 53 (createBaseVisualization): passes `async () => new BaseVisType(config)` as the factory → double wrapping
- types_service.ts lines 60-66 (createBaseVisualizationAsync): correctly passes factory directly, no double wrapping
- base_vis_type.ts constructor: applies `defaultsDeep` for visConfig, editorConfig, options — these would be called twice on the same data in the double-wrap case

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nreese this comment seems legit, in the registerVisualization function you are calling:

this.types[name] = async () => {
      const config = await getVisDefinition();
      return new BaseVisType(config);
    };

so basically new BaseVisType(new BaseVisType(config))

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 4, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 27, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 27, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 27, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 28, 2026

/ci

@nreese
Copy link
Copy Markdown
Contributor Author

nreese commented May 28, 2026

/ci

@nreese nreese changed the title Async vis types [legacy visualization] Register vis type with async callback May 28, 2026
@nreese nreese marked this pull request as ready for review May 28, 2026 17:35
@nreese nreese requested review from a team as code owners May 28, 2026 17:35
@nreese nreese added Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t// release_note:skip Skip the PR/issue when compiling release notes labels May 28, 2026
@nreese nreese added backport:version Backport to applied version labels v9.5.0 labels May 28, 2026
@infra-vault-gh-plugin-prod
Copy link
Copy Markdown

Pinging @elastic/kibana-visualizations (Team:Visualizations)

@markov00 markov00 self-requested a review May 29, 2026 09:07
Comment on lines 53 to +57
createBaseVisualization: <TVisParams extends VisParams>(
config: VisTypeDefinition<TVisParams>
): void => {
const vis = new BaseVisType(config);
this.registerVisualization(vis);
this.registerVisualization(config.name, async () => new BaseVisType(config));
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nreese this comment seems legit, in the registerVisualization function you are calling:

this.types[name] = async () => {
      const config = await getVisDefinition();
      return new BaseVisType(config);
    };

so basically new BaseVisType(new BaseVisType(config))

Comment thread src/platform/plugins/shared/visualizations/public/vis_async.ts Outdated
Comment on lines +503 to +504
getIconForSavedObject: () => {
return 'visualizeApp';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you change this, in the Dashboard Add from library you will lose the chart icons.
Not a big deal for the agg-base visualize charts, but Vega will be affected to.

Before:
Image
After:
Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you change this, in the Dashboard Add from library you will lose the chart icons.
Not a big deal for the agg-base visualize charts, but Vega will be affected to.

I think this is ok and even beneficial in the long run. There are plans to create a vega embeddable in the near future for "as code". This change will make it clear when a vega library item is vega embeddable or legacy vega vis.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see but for now I don't think this is a valid change, it will create confusion until we don't have the vega embeddable is ready. We should not release this change until Vega is not clearly distinguishable from the rest.

Copy link
Copy Markdown
Contributor

@markov00 markov00 Jun 1, 2026

Choose a reason for hiding this comment

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

You can just hardcode a check for the visState.type and return the Vega icon if vega and visualizeApp otherwise. We don't really need the registry for that. We can then clean this up with the vega embeddable

Comment thread src/platform/plugins/shared/visualizations/public/utils/saved_visualize_utils.ts Outdated
Comment on lines +39 to +43
private getAll = async () => {
return await asyncMap(Object.values(this.types), async (getVisType) => {
return await getVisType();
});
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: The asyncMap propagates the first rejection, so if a dynamic import fails everything is rejected (this differs a bit from the previous logic if I understood it correcly)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How would you like this resolved? Promise.all?

@nreese nreese requested a review from markov00 May 29, 2026 16:48
@kibanamachine
Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #133 / Cloud Security Posture - Group 5 (KSPM + Flyouts) Security Alerts Page - Graph visualization expanded flyout - filter by node

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
maps 1384 1382 -2
visTypeVega 1839 1840 +1
total -1

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
maps 3.2MB 3.2MB +13.4KB
visTypeVega 2.0MB 2.0MB +34.9KB
visualizations 335.3KB 335.8KB +522.0B
total +48.9KB

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
maps 41.1KB 32.2KB -8.9KB
visTypeVega 35.8KB 10.3KB -25.5KB
visualizations 46.3KB 46.6KB +232.0B
total -34.2KB
Unknown metric groups

async chunk count

id before after diff
maps 36 37 +1
visTypeVega 5 7 +2
total +3

History

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

Labels

backport:version Backport to applied version labels release_note:skip Skip the PR/issue when compiling release notes Team:Visualizations Team label for Lens, elastic-charts, Graph, legacy editors (TSVB, Visualize, Timelion) t// v9.5.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants