Skip to content

[Banner Plugin] Dynamic Configuration Support for the Banner Plugin #10326

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

sidwang42
Copy link
Contributor

@sidwang42 sidwang42 commented Aug 3, 2025

Description

This PR introduces dynamic configuration support for the banner plugin.
A new optional externalLink setting allows OpenSearch Dashboards to fetch banner configuration
from a remote JSON endpoint. If fetching fails, the system falls back to local YAML or Advanced Settings.
This enables centralized banner management across multiple instances.

Issues Resolved

#9990

Screenshot

No direct UI changes, but banner content, style, and visibility can now
be updated dynamically from an external configuration endpoint.

Testing the changes

  1. Add the following entry to opensearch_dashboards.yml:
    banner.externalLink: "https://example.com/banner-config.json"

  2. Start OpenSearch Dashboards.

  3. Verify the banner loads configuration from the provided endpoint.

  4. Simulate a failed request (e.g., invalid URL) — confirm fallback to local configuration.

  5. Run unit tests in fetch_external_config.test.ts:
    yarn test:jest src/plugins/banner/server/routes/fetch_external_config.test.ts

  6. Validate both HTTP and HTTPS URLs work, including timeout and parsing error scenarios.

Changelog

  • feat: Support dynamic banner config via external JSON endpoint with local fallback

Check List

  • All tests pass

    • yarn test:jest
    • yarn test:jest_integration
  • New functionality includes testing.

  • New functionality has been documented.

  • Update [CHANGELOG.md](./../CHANGELOG.md)

  • Commits are signed per the DCO using --signoff

opensearch-changeset-bot bot added a commit to sidwang42/OpenSearch-Dashboards that referenced this pull request Aug 3, 2025
Copy link

codecov bot commented Aug 4, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.84%. Comparing base (b886e61) to head (173c445).
⚠️ Report is 7 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10326      +/-   ##
==========================================
+ Coverage   61.76%   61.84%   +0.08%     
==========================================
  Files        4242     4255      +13     
  Lines      107874   108350     +476     
  Branches    17572    17725     +153     
==========================================
+ Hits        66623    67012     +389     
- Misses      36775    36827      +52     
- Partials     4476     4511      +35     
Flag Coverage Δ
Linux_1 27.64% <ø> (-0.01%) ⬇️
Linux_2 41.23% <ø> (ø)
Linux_3 39.60% <100.00%> (+0.06%) ⬆️
Linux_4 33.01% <ø> (+0.24%) ⬆️
Windows_1 27.66% <ø> (-0.01%) ⬇️
Windows_2 41.20% <ø> (ø)
Windows_3 39.60% <100.00%> (+0.06%) ⬆️
Windows_4 33.01% <ø> (+0.24%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@zhongnansu zhongnansu added the banner Dashboards banner plugin which displays global banner and allows customization label Aug 5, 2025
},
(res) => {
if (res.statusCode !== 200) {
logger.error(`Error fetching banner config: HTTP status ${res.statusCode}`);
Copy link
Member

Choose a reason for hiding this comment

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

let's also log the error message for troubleshooting

return new Promise((resolve) => {
try {
const parsedUrl = new URL(url);
const requestModule = parsedUrl.protocol === 'https:' ? https : http;
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 force https for security

const parsedUrl = new URL(url);
const requestModule = parsedUrl.protocol === 'https:' ? https : http;

const req = requestModule.request(
Copy link
Member

Choose a reason for hiding this comment

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

Can we use fetch() in Node 18+ (or node-fetch), which handles timeouts, errors, and streaming internally?


let data = '';
res.on('data', (chunk) => {
data += chunk;
Copy link
Member

Choose a reason for hiding this comment

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

where do we check the json endpoint format is matching to what we required?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

where do we check the json endpoint format is matching to what we required?

@zhongnansu
Haven’t added this yet, as I was assuming all content would come from a system-admin-set value, so no check was needed.

But thinking about potential issues like malformed JSON, missing required fields, or incorrect data types causing runtime errors or unexpected UI behavior, it would be great to add a validation check here.

Copy link
Member

Choose a reason for hiding this comment

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

system-admin-set values set by human, we don't assume things are working as expected, we prevent things from going south.

@@ -28,4 +28,5 @@ export const configSchema = schema.object({
size: schema.oneOf([schema.literal('s'), schema.literal('m')], {
defaultValue: 'm',
}),
externalLink: schema.maybe(schema.string()),
Copy link
Member

Choose a reason for hiding this comment

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

is there a schema for url? If not, can we add url validation?

// Check externalLink
if (config.externalLink !== undefined && typeof config.externalLink !== 'string') {
logger.error(
`Invalid banner config: 'externalLink' must be a string, got ${typeof config.externalLink}`
Copy link
Member

Choose a reason for hiding this comment

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

not just a valid string, it should be a valid url

* @param logger Logger instance for logging errors
* @returns The banner configuration or null if there was an error
*/
export async function fetchExternalConfig(
Copy link
Member

Choose a reason for hiding this comment

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

this function does not only fetch, but also validate. Consider extract the validation logic, and make 2 function calls, one to fetch, one for validate content

* @param logger Logger instance for logging errors
* @returns Whether the configuration is valid
*/
export function validateBannerConfig(
Copy link
Member

Choose a reason for hiding this comment

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

How do we ensure that invalid fields are not passed in?
For example

{
  content: "validstring"
  somethingBad: "some bad scripts that should not be passed in"
}

Copy link
Member

@zhongnansu zhongnansu left a comment

Choose a reason for hiding this comment

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

codecov CI is failing, could you check if we need more tests?

zhongnansu
zhongnansu previously approved these changes Aug 12, 2025
Copy link
Member

@zhongnansu zhongnansu left a comment

Choose a reason for hiding this comment

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

lgtm

Comment on lines 60 to 62
if (externalConfig) {
// Validate the configuration
if (!validateBannerConfig(externalConfig, bannerSetup.logger)) {
Copy link
Member

Choose a reason for hiding this comment

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

instead of nesting these if statements, can we invert the logic so it's more readable? Eg:

if (!externalConfig) { 
  // Failed to fetch from URL 
  // return response
}

if (!validateBannerConfig) { 
  // invalid config 
  // return response
}

// return with externalconfig

Copy link
Contributor Author

Choose a reason for hiding this comment

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

instead of nesting these if statements, can we invert the logic so it's more readable? Eg:

if (!externalConfig) { 
  // Failed to fetch from URL 
  // return response
}

if (!validateBannerConfig) { 
  // invalid config 
  // return response
}

// return with externalconfig

Great Point. Updated accordingly. 😄

let isValid = true;

// Check for unexpected fields
const validFields = [
Copy link
Member

Choose a reason for hiding this comment

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

Correct me if I'm wrong but we defined a schema somewhere within the banner plugin codebase right? Could we not use that schema to validate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@huyaboo

Correct me if I'm wrong but we defined a schema somewhere within the banner plugin codebase right? Could we not use that schema to validate?

I aim for the same — having a centralized control of the allowed schema — but it doesn’t seem possible in this case. The configSchema in config.ts is only applied by the platform during YAML load at plugin startup. For dynamic configs (e.g., loaded from link or other sources at runtime), there’s no built-in hook to reuse that schema object directly for validation without re-implementing it, because @osd/config-schema is tightly coupled to the initial config lifecycle rather than arbitrary runtime input.

@zhongnansu zhongnansu requested a review from huyaboo August 16, 2025 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
banner Dashboards banner plugin which displays global banner and allows customization repeat-contributor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants