-
Notifications
You must be signed in to change notification settings - Fork 1.1k
[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
base: main
Are you sure you want to change the base?
[Banner Plugin] Dynamic Configuration Support for the Banner Plugin #10326
Conversation
Signed-off-by: Sid Wang <[email protected]>
a22f824
to
ef701d1
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
}, | ||
(res) => { | ||
if (res.statusCode !== 200) { | ||
logger.error(`Error fetching banner config: HTTP status ${res.statusCode}`); |
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.
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; |
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.
we should force https for security
const parsedUrl = new URL(url); | ||
const requestModule = parsedUrl.protocol === 'https:' ? https : http; | ||
|
||
const req = requestModule.request( |
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.
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; |
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.
where do we check the json endpoint format is matching to what we required?
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.
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.
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.
system-admin-set values set by human, we don't assume things are working as expected, we prevent things from going south.
Signed-off-by: Sid Wang <[email protected]>
Signed-off-by: Sid Wang <[email protected]>
src/plugins/banner/server/config.ts
Outdated
@@ -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()), |
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.
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}` |
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.
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( |
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 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( |
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.
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"
}
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.
codecov CI is failing, could you check if we need more tests?
Signed-off-by: Sid Wang <[email protected]>
Signed-off-by: Sid Wang <[email protected]>
Signed-off-by: Sid Wang <[email protected]>
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.
lgtm
if (externalConfig) { | ||
// Validate the configuration | ||
if (!validateBannerConfig(externalConfig, bannerSetup.logger)) { |
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.
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
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.
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 = [ |
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.
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?
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.
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.
Signed-off-by: Sid Wang <[email protected]>
Description
This PR introduces dynamic configuration support for the banner plugin.
A new optional
externalLink
setting allows OpenSearch Dashboards to fetch banner configurationfrom 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
Add the following entry to
opensearch_dashboards.yml
:banner.externalLink: "https://example.com/banner-config.json"
Start OpenSearch Dashboards.
Verify the banner loads configuration from the provided endpoint.
Simulate a failed request (e.g., invalid URL) — confirm fallback to local configuration.
Run unit tests in
fetch_external_config.test.ts
:yarn test:jest src/plugins/banner/server/routes/fetch_external_config.test.ts
Validate both HTTP and HTTPS URLs work, including timeout and parsing error scenarios.
Changelog
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