|
| 1 | +# UI Settings |
| 2 | + |
| 3 | +OpenSearch Dashboards provide global, user, and workspace-level UI settings, which govern the overall behavior of the application. Global and user scope settings are stored in separate documents, while workspace-scoped UI settings are stored within the workspace itself, as they serve as workspace-isolated objects. |
| 4 | + |
| 5 | +This doc explains the new multi-scope UI settings management and compares it with the previous implementation. |
| 6 | + |
| 7 | +## Background |
| 8 | + |
| 9 | +In the previous implementation, we assumed that most types of settings had only a single scope. However, there is an exception— default data source, which has two scopes: workspace and global. |
| 10 | +When processing workspace-scoped settings, we follow these rules: |
| 11 | + |
| 12 | +* When retrieving UI settings within a workspace, the system merges the workspace UI settings with the global UI settings. If the same setting is defined in both places, the workspace UI setting takes priority. |
| 13 | +* When updating UI settings within a workspace, only the workspace UI settings are modified, while the global UI settings remain unchanged. |
| 14 | + |
| 15 | +We handle the above logic in workspace_ui_settings_client_wrapper, where we explicitly identify the default data source and apply hardcoded logic to override. Although this approach works, it's not ideal, as the number of multiple scoped settings may continue to grow, leading to increased complexity and reduced flexibility. |
| 16 | + |
| 17 | +To support multi-scope UI settings, we need to address the client-side and server-side aspects of the UI settings architecture. On the server side, we already provide a set of CRUD APIs that support scopes, for example, setMany and getUserProvide. However, on the public side, none of the operations currently make use of scopes. |
| 18 | + |
| 19 | +Each time a page loads, the server-side rendering service performs a comprehensive get operation, retrieving all UI settings—both default values and user-provided values across user, workspace, and global scopes—and merges them. The default settings and user-provided settings are combined, and the injectedMetadata plugin retrieves this merged result via getLegacyMeta. Ui settings client caches these results internally. As a result, subsequent get operations read directly from this cache for efficiency. When a set operation is performed, client flushes the cached changes and sends a batched update to the server with api setMany. Once the update succeeds, the local cache is updated to reflect the changes. So, in such approach, scope handling is ignored on the public side and deferred to the server, which is responsible for determining the appropriate scope for each UI settings update. |
| 20 | + |
| 21 | + |
| 22 | +The entire flow can be summarized as follows: |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | + |
| 27 | +## Implementation |
| 28 | + |
| 29 | +To support multi-scoped UI settings and ensure that scope is properly utilized on both the public and server sides, the implementation can be divided as follows: |
| 30 | + |
| 31 | +### Enhance the scope handling in server-side ui settings client |
| 32 | + |
| 33 | +Most of the time, we won’t explicitly pass a scope, since most settings only exist within a single scope. In such cases, we delegate the scope resolution logic to the server. |
| 34 | + |
| 35 | +*Read*: |
| 36 | +Server-side user settings client exposes a getUserProvided method to handle reads. Regardless of whether a specific key is being requested or not, the general logic is to retrieve all user-provided settings across supported scopes or all scopes. The resolution logic follows these rules: |
| 37 | + |
| 38 | + 1. Read with scope: If a scope is explicitly passed, validate first and return settings directly from the specified scope. |
| 39 | + 2. Read without scope: if not, continue to follow the established precedence rules: resolving settings in the order of user → workspace → global, values from higher-priority scopes override those from lower-priority ones. |
| 40 | + 3. Read multi-scope settings without scope: (1) If all scopes define a value for a given setting, the system will resolve the final value based on scope priority.(2) if the highest-priority scope does not provide a value, the system will fall back to the lower-priority scope(most time global scope) when merging. In such cases, when public side intends to read the multi-scope settings, it should explicitly pass the desired scope to ensure accurate retrieval. |
| 41 | + |
| 42 | +Specifically for workspace-scoped UI settings, we refactored workspace_ui_settings_client_wrapper. If the user is not currently in a workspace, the wrapper will throw a GenericNotFoundError, which is caught and handled by the ui_settings_client, returning an empty {} . |
| 43 | +If the user is in a workspace, the wrapper performs the following steps: |
| 44 | + |
| 45 | + 1. Retrieves the workspace saved object. |
| 46 | + 2. Retrieves the global config. |
| 47 | + 3. Overrides the global config attributes with the workspaceObject.attributes.uiSettings |
| 48 | + |
| 49 | +*Write* |
| 50 | +When updating settings, the server must determine the correct scope for each setting being written. The write logic should follow these rules: |
| 51 | + |
| 52 | + 1. Write with scope: If a scope is explicitly passed, validate first and update settings directly to the specified scope. |
| 53 | + 2. Write without scope: |
| 54 | + 1. If the setting defines a single scope in registration, honor the scope defined. |
| 55 | + 2. If the setting does not define a scope in registration, we will use global to keep backward compatibility. |
| 56 | + 3. Write multi-scope settings without scope: show a deprecation warning for now and will throw error in next major release because UI setting does not know which scope it should use. |
| 57 | + |
| 58 | +When updating settings without an explicitly provided scope, ui_settings_client will first group the changes by scope—separating them into workspace, user, and global scopes. This is determined by scope definitions provided during the UI setting registration phase. If a setting does not explicitly define a scope, it is treated as global scope by default. |
| 59 | +Specifically for workspace-scoped UI settings—since they are stored within the workspace's saved object—we will refactor the workspace_ui_settings_client_wrapper to specifically intercept workspace-scoped setting updates by identifying keys prefixed with current_workspace. |
| 60 | +The wrapper handles two key logic: |
| 61 | + |
| 62 | + * If the user is not currently in a workspace, the wrapper will throw a BadRequestError. |
| 63 | + * If the user is in a workspace, the wrapper performs the following steps: |
| 64 | + |
| 65 | + 1. updates the workspace saved object. |
| 66 | + 2. Retrieves the global config. |
| 67 | + 3. Overrides the global config attributes with the updated workspaceObject.attributes.uiSettings |
| 68 | + |
| 69 | +### Support scope in public side |
| 70 | + |
| 71 | +Enhance ui_settings_client methods to support scope. |
| 72 | + |
| 73 | +1. We initialize multiple ui_settings_apis—one for each scope—and one as default to handle cases where no scope is provided. |
| 74 | +2. We will introduce a new method for ui_settings_client, getUserProvidedWithScope, which is specifically responsible for sending GET requests to the server based on the currently specified scope. |
| 75 | +3. Add scope support to ui_settings_client.set. Specifically, within ui_settings_client, there will be a check: if the current setting is multi-scoped, then after a successful update, the client will refresh its cache by fetching settings from all scopes and merging them using the default ui_settings_api. |
| 76 | + |
| 77 | +The entire flowcan be summarized as follows: |
| 78 | + |
| 79 | + |
0 commit comments