feat: Read settings from props/server config when available#1558
feat: Read settings from props/server config when available#1558Zhou-Ziheng merged 27 commits intodeephaven:mainfrom
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #1558 +/- ##
==========================================
- Coverage 46.66% 46.63% -0.03%
==========================================
Files 591 591
Lines 36394 36406 +12
Branches 9108 9113 +5
==========================================
- Hits 16984 16979 -5
- Misses 19358 19375 +17
Partials 52 52
Flags with carried forward coverage won't be shown. Click here to find out more.
☔ View full report in Codecov by Sentry. |
| LocalWorkspaceStorage.getServerConfigValueOrUseDefault( | ||
| serverConfigValues, | ||
| 'dateTimeFormat', | ||
| DateTimeColumnFormatter.DEFAULT_DATETIME_FORMAT_STRING | ||
| ), |
There was a problem hiding this comment.
Instead of setting the value, we should leave it undefined; that way if the server value is updated again, user gets the new value unless they've explicitly updated the option before.
That's how we handle it on Enterprise, anyway: https://github.com/deephaven-ent/iris/blob/b41e56506498118f27fed79ccac41f3dc2af4617/web/client-ui/src/dashboard/WorkspaceStorage.ts#L174
https://github.com/deephaven-ent/iris/blob/b41e56506498118f27fed79ccac41f3dc2af4617/web/client-ui/src/settings/FormattingSectionContent.tsx#L857
From a previous commit I did: https://github.com/deephaven-ent/iris/commit/28a75ae75b27bf97fa1046418af89aa16986e412
mofojed
left a comment
There was a problem hiding this comment.
Can you include the steps you took to configure the server/test different default values being read?
| export const saveSettings = | ||
| ( | ||
| settings: WorkspaceSettings | ||
| settings: Partial<WorkspaceSettings> | ||
| ): ThunkAction< | ||
| Promise<Workspace>, | ||
| Promise<CustomizableWorkspace>, | ||
| RootState, | ||
| never, | ||
| PayloadAction<unknown> | ||
| > => | ||
| dispatch => | ||
| dispatch(updateWorkspaceData({ settings })); |
There was a problem hiding this comment.
I'd rather you make this a new action updateSettings rather than saveSettings. Otherwise there's no way to delete an old setting.
There was a problem hiding this comment.
old settings can be deleted by setting it's value to undefined. Although I do agree updateSettings make more sense semantically
| // this will only be null on initial render | ||
| return null as never; |
There was a problem hiding this comment.
We should just throw in this case ...
| // this will only be null on initial render | |
| return null as never; | |
| throw new Error('getWorkspace called before workspace was set') |
Then in AppInit we should catch that error in the mapStateToProps and set workspace to null there. That will achieve two things:
- A more descriptive error about how to fix the issue rather than just getting a possibly inexplicable NPE
- More accurate depiction in
AppInitof what's going on with theworkspaceprop. Right now it's defined as justWorkspacebut we're still expecting/checking if it'snullto know ifisLoadingis still ongoing, which is kind of ignoring the type checking.
| const defaultInjectedWorkspace: Workspace = { | ||
| data: { | ||
| ...workspace.data, | ||
| settings: getDefaultWorkspaceSettings(store) ?? {}, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Right idea, but I think we're injecting at the wrong spot. We should inject just the settings in getSettings instead of injecting whenever the workspace is fetched.
Also, we should memoize the result.
There was a problem hiding this comment.
I wonder if we are fetching the entire workspace then manually destructure the settings object from it somewhere. My original intention was so that the Workspace object would look identical to before the change, to avoid creating unintended changes elsewhere
| const keys = Object.keys( | ||
| customizedWorkspaceSettings | ||
| ) as (keyof WorkspaceSettings)[]; | ||
|
|
||
| for (let i = 0; i < keys.length; i += 1) { | ||
| const key = keys[i]; | ||
| const value = customizedWorkspaceSettings[key]; | ||
| // only set pass in customized defaults if defined | ||
| if (value !== undefined) { | ||
| defaultInjectedWorkspace.data.settings[key] = | ||
| value as WorkspaceSettings[typeof key] as never; | ||
| } | ||
| } |
There was a problem hiding this comment.
Unsure why you wrote it this way - pretty sure when injecting in getSettings, you could just do something like:
export const getSettings = <State extends RootState>(
store: State
): WorkspaceSettings => {
const defaultSettings = getDefaultWorkspaceSettings(store) ?? EMPTY_OBJECT;
const userSettings = getWorkspace(store).data.settings;
return {
...defaultSettings,
...userSettings,
};
};
There was a problem hiding this comment.
This was because sometimes the userSettings have values intentionally set to undefined (not just missing) to let user reset values to default etc. setting a value to undefined means the value is still keyed in userSettings so doing object spreading would make those values undefined as opposed to the default.
| defaultDateTimeFormat?: string; | ||
| showTimeZone?: boolean; | ||
| showTSeparator?: boolean; | ||
| timeZone?: string; | ||
| truncateNumbersWithPound?: boolean; | ||
| saveSettings: (settings: Partial<WorkspaceSettings>) => void; | ||
| defaultDecimalFormatOptions?: FormatOption; | ||
| defaultIntegerFormatOptions?: FormatOption; |
There was a problem hiding this comment.
None of these should be optional, the value we get from redux should always be defined.
There was a problem hiding this comment.
This was mostly wrestling with TypeScript. I had to change the Redux state type for workspace.data.settings to be optional (because it really is optional now), so the getWorkspace's return value is now typed as CustomizableWorkspace instead of Workspace, marking these parameters optional. I'll investigate for a workaround
| saveSettings: (settings: Partial<WorkspaceSettings>) => void; | ||
| defaultDecimalFormatOptions?: FormatOption; | ||
| defaultIntegerFormatOptions?: FormatOption; | ||
| defaults: { |
There was a problem hiding this comment.
defaults we should be able to just map to getDefaultWorkspaceSettings instead of redefining defaults here.
| assertNotNull(showTimeZone); | ||
| assertNotNull(showTSeparator); | ||
| assertNotNull(timeZone); | ||
| assertNotNull(truncateNumbersWithPound); | ||
| assertNotNull(defaultDateTimeFormat); | ||
| assertNotNull(defaultDecimalFormatOptions); | ||
| assertNotNull(defaultIntegerFormatOptions); |
There was a problem hiding this comment.
None of these should be optional/you shouldn't need to assert against null
| IntegerColumnFormatter.makeCustomFormat(event.target.value) | ||
| ) | ||
| ) { | ||
| this.commitChanges(update); |
There was a problem hiding this comment.
We should still debounce these changes, as they could have implications for the entire UI (e.g. if you have a table open and you're updating the formatting, it'll update every cell of every table open with every keystroke).
See my other comment in this file about debouncing for a suggestion.
|
This is the the only thing of importance is the integerFormat |
mofojed
left a comment
There was a problem hiding this comment.
Still a few issues with functionality, I think I've pointed out all the problematic areas but here is a test case that I think should be comprehensive to illustrate what the issue is. This assumes starting from an incognito tab, so no previous workspace data
- Set
integerFormat=##000in props - Login as user and check in the Settings menu
- Integer format option should show
##000and not have the "Reset Integer Format" button visible, since we are using the default value
- Shut down the server, set
integerFormat=##111in props - From the same browser as step 2 (so workspace data is intact), login and check the settings tab
- Integer format option should show
##111and not have the "Reset Integer Format" visible
- Change the option to
##999. Reset button should become visible - Shut down the server, set
integerFormat=##222in props - Login and check settings tab
- Integer format should show
##999as you set in step 5. Reset button should be visible
- Click the Reset button - the integer format should change to
##222and reset button should disappear.
You can also inspect what you have set in Redux using the Redux dev tools, and what's stored in LocalStorage using the browser dev tools. You want to make sure the default settings in redux are populated with the settings read from the server (anything not defined by the server should fallback to whatever previous defaults we have set right now), and workspace storage settings should only have the options the user has changed.
mofojed
left a comment
There was a problem hiding this comment.
Some minor cleanup, but looks great! Thanks
Updated the follow values to use ServerConfig when possible:
To test, first create a config file (see https://deephaven.io/core/docs/how-to-guides/configuration/config-file/), then add
- ./deephaven.prop:/opt/deephaven/config/deephaven.propas a volumn for services.deephaven.columns
BREAKING CHANGE: saveSettings action has been replaced with
updateSettings, which now just takes partial settings instead