Skip to content
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

Connect: Generate json schema for app config #22538

Merged
merged 23 commits into from
Mar 7, 2023

Conversation

gzdunek
Copy link
Contributor

@gzdunek gzdunek commented Mar 2, 2023

Part of #21914

This PR adds JSON schema generation for our config schema (and slightly extends the FileStorage API).
I was considering using fs.write* API in the updateJsonSchema to write the file, but I think using a FileStorage object has more advantages:

  • In updateJsonSchema we would need a path to the user data dir, probably the easiest way would to be pass the entire RuntimeSettings there; with FileStorage we can avoid it.
  • The json schema is a JS object that has to be stringified to JSON (in a pretty way, if possible) - FileStorage does both.
  • We keep files we write to in one place.
  • Testing is easier IMO, we don't have to mock fs functions.

image

@gzdunek gzdunek requested review from ravicious and avatus March 2, 2023 13:25
@github-actions github-actions bot requested review from kimlisa and ryanclark March 2, 2023 13:26
@gzdunek gzdunek removed request for ryanclark and kimlisa March 2, 2023 13:26
@gzdunek gzdunek force-pushed the gzdunek/enable-keyboard-shortcuts-configuration branch 2 times, most recently from 3227260 to 5fda1e7 Compare March 3, 2023 13:22
@gzdunek gzdunek force-pushed the gzdunek/generate-json-schema branch from e1bcc53 to 2d905a9 Compare March 3, 2023 13:56
@gzdunek gzdunek marked this pull request as draft March 3, 2023 14:30
@gzdunek gzdunek force-pushed the gzdunek/generate-json-schema branch from 260072d to e4ca2b8 Compare March 3, 2023 14:30
@gzdunek gzdunek changed the base branch from gzdunek/enable-keyboard-shortcuts-configuration to master March 3, 2023 14:31
@gzdunek gzdunek marked this pull request as ready for review March 3, 2023 14:31
@github-actions github-actions bot requested review from rudream and ryanclark March 3, 2023 14:32
@gzdunek gzdunek removed request for rudream and ryanclark March 3, 2023 14:32
): void {
const schemaField = configFileStorage.get('$schema');
if (schemaField !== jsonSchemaFilename) {
configFileStorage.put('$schema', jsonSchemaFilename);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the config file has a syntax error, this will cause overriding it with an empty object containing only $schema field.
I will handle this case in the next PR.

Copy link
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

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

Wow, editing the config with this feels like magic. My VSCode schema detection is broken and it always puts the same default everywhere but my vim plugin works as expected.

I left some comments regarding the structure of the code because my mental fortitude this week is super weak and realizing why you need to have a getter for the file path on the file storage took me way to long to realize and I had to jump between too many files! I agree with the decision regarding getFilePath described in the PR description though.

I might have some more comments next week.

yarn.lock Show resolved Hide resolved
web/packages/teleterm/src/main.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved
web/packages/teleterm/src/main.ts Show resolved Hide resolved
@ravicious ravicious self-requested a review March 3, 2023 15:35
@gzdunek
Copy link
Contributor Author

gzdunek commented Mar 6, 2023

I noticed that subsequent keyboard shortcuts refer to the first one. That's because we are reusing the same schema object for them.

 "keymap.tab1": {
      "type": "string",
      "default": "Command+1",
      "description": "Shortcut to open tab 1. A valid shortcut contains at least one modifier and a single key code, for example \"Shift+Tab\". Function keys do not require a modifier."
    },
    "keymap.tab2": {
      "$ref": "#/properties/keymap.tab1",
      "default": "Command+2",
      "description": "Shortcut to open tab 2. A valid shortcut contains at least one modifier and a single key code, for example \"Shift+Tab\". Function keys do not require a modifier."
    },

I think it looks quite weird, so we should either:

IMO the second option is fine.

@ravicious
Copy link
Member

I'm also partial to the second option.

Correct me if I'm wrong, but I've assumed that you can compose zod schemas however you want into new schemas – I mean that's how the API of zod is structured, you have things like z.string().or(z.number()). So IMHO it only follows that calling .default on an existing schema creates a new one. Perhaps zod-to-json-schema tries to be a little too smart here, possibly because other than the default value the schemas are structurally the same.

@gzdunek
Copy link
Contributor Author

gzdunek commented Mar 6, 2023

Yeah, I think it is a smart mechanism that looks for the same references (?) in the schema:

Resolves recursive and recurring schemas with internal $refs.

(and AFAIK we can compose schemas as we want)

web/packages/teleterm/src/services/config/configService.ts Outdated Show resolved Hide resolved

export type AppConfig = z.infer<ReturnType<typeof createAppConfigSchema>>;

export const createAppConfigSchema = (platform: Platform) => {
Copy link
Member

Choose a reason for hiding this comment

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

Off topic nit but lately I've been reconsidering if putting the verbs in the names of the files themselves is a good idea. I feel like inside this dir we could have appConfigSchema.ts and keyboardShortcutSchema.ts and it'd be totally fine. We also don't have that many files starting with get/create in the project.

It'd also help clean up the naming between createAppConfigSchema and getKeyboardShortcutSchema, at least on the file name level. 😏 I think I prefer the verb "create" or "make" for this scenario over "get" as what we're really doing here is instantiating something new.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I renamed getKeyboardShortcutSchema too :)

I don't have a strong opinion on the file names, and even in a services/config/ module we were inconsistent, for example a function createConfigService was exported from configService.ts file.

Sometimes naming the file the same as a main/only export makes sense for me, for example in tshd/createClient.ts (maybe I'm just used to it). Would you see it as tshd/client.ts?

Copy link
Member

Choose a reason for hiding this comment

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

Would you see it as tshd/client.ts?

I suppose so? I don't have a hard opinion on this atm, but overall I'm thinking about shifting focus when naming from particular functions to overall concepts.

If JS was more opinionated about imports and files then it'd all be easier. For example each file in Elm is a separate module with a specific namespace, so you could have ConfigService.elm and you'd expect to find a function called ConfigService.init. Inside ConfigService.elm, you can use init without the namespace qualifier. Outside of that module, you typically import the whole module and call ConfigService.init but you can also import a specific function without the namespace qualifier or use an alias for the namespace.

In JS there's no notion of this namespace which makes things harder.


In general I think I'd aim at reducing the number of files we create, each function doesn't have to live in a separate file. Take a look at e/web/teleport/src/services/recovery/makeRecoveryToken.ts, this function could as well live in e/web/teleport/src/services/recovery/recovery.ts.

Bigger functions such as web/packages/teleport/src/services/recordings/makeRecording.ts could live in recording.ts as well, especially since they define functions that are related to a recording but might be useful outside of the context of makeRecording, for example formatDuration.

This all seems easy and clear as long as we we're talking about non-React code. As soon as we enter React then we potentially have a conflict between recovery.ts and a file for a component called Recovery.

@gzdunek gzdunek added this pull request to the merge queue Mar 7, 2023
Merged via the queue into master with commit 9967149 Mar 7, 2023
@public-teleport-github-review-bot

@gzdunek See the table below for backport results.

Branch Result
branch/v12 Failed

gzdunek added a commit that referenced this pull request Mar 16, 2023
* Rename `putAllSync` to `writeSync`

* Extend `FileStorage` with functions to replace an entire state and return file path used to create it

* Add `updateJsonSchema` function

* Generate schema for the app config

* Always return a string from `createMockFileStorage().getFilePath()`

* Add missing license headers

* Rename 'teleport_connect_config_schema.json' to 'schema_app_config.json'

* Rename `getKeyboardShortcutDescription` to `getShortcutDesc`

* Rename `keyboardShortcutSchema` to `shortcutSchema`

* Update a valid shortcut message

* Rename `configJsonSchemaFile` to `jsonSchema`

* Simplify `updateJsonSchema`

* Set `$refStrategy` to none

* Add missing description for `usageReporting.enabled`

* Bump zod to the latest (improves TS performance)

* Move `configService` implementation to `configStore`

* Rename `configStore` to `configService`

* Move `updateJsonSchema` to `createConfigService`

* Move `validateStoredConfig` outside `createConfigService`

* Rename `createAppConfigSchema.ts` to `appConfigSchema.ts`, `getKeyboardShortcutSchema.ts` to `keyboardShortcutSchema.ts`

* Export `createKeyboardShortcutSchema`

* Add license header

(cherry picked from commit 9967149)
gzdunek added a commit that referenced this pull request Mar 17, 2023
* Connect: Prepare keyboard shortcuts configuration (#22193)

* Make naming around keyboard shortcuts more consistent

Previously, we used words like: "key", "type", "shortcut" which were really confusing (and I wrote this code), because it was hard to tell what exactly they describe.
From now, we will use only "accelerator" (borrowed from Electron) and "action" words.
For example, in a shortcut "Command+1": "tab-1":
- "Command+1" is "accelerator"
- "tab-1" is "action"

* Use "+" instead of "-" as a separator

With "-" we are not able to use this symbol as a valid key code.

* Rename some config keys to follow the same naming pattern

* Rename keyboard shortcuts to match config keys

* Extract `mapAcceleratorsToActions` to a function

* Rename other "keyboard shortcuts" to "accelerators" and "actions"

(cherry picked from commit 717c4fa)

* Connect: Enable keyboard shortcuts configuration (#22194)

* Use event.code for non A-z keys

Without this fix, shortcuts like "Shift+1" can't work, because the reported character is "!".

* Add Zod schema to validate keyboard shortcuts

* Render better formatted error notification, add a link to docs

* Notify about duplicated accelerators

* Improve the error message for re-parsing failure (it can happen when a default value does not pass validation)

* A bunch of renames

* Allow spaces in accelerators

* Rename `isContentAnObject`

* Allow `Notification` to render links and lists

* Split by optional whitespace and "+"

* Allow lowercase letters

* Require a modifier for non-function keys

* Move VALID_SHORTCUT_MESSAGE to `initUi`

* Always return from `getDuplicateAccelerators`

* Use 'Cmd' and 'Ctrl' for mac

* Add comments

* Fix notification not rendering content

* Add more comments, rename `getKeyCode` to `getKeyName`

* Fix incorrect size of <Link> text

* Remove "expected" prefix

* Revert `typeof content === 'object'` in `Notification`

* Remove a comment about disabled keys in `ConfigService`, add a note about `keymap.` prefix

* Improve `getKeyName` comment

* Extract an inline object to a variable in `getKeyName`

* Fix notification list padding

* Change text for doc link & description for config error

* Improve comment for `getKeyName`

* Remove special formatting for list === 1 in `Notification`

* Print valid modifiers

* Call `getKeyboardShortcutSchema()` once

* Collect issues from all validations, run `invalidModifiers` through a set

* Change error message for `missingModifierIssue`

* Convert duplicates warning to error

* Define ALLOWED_KEY_CODES in a more concise way

* Support both `IntlBackslash` and `Backquote`

* Restore modifiers for mac to full spelling, sort them in order recommended by platform guidelines

* Fix old comment about the shortcuts order

---------

Co-authored-by: Rafał Cieślak <[email protected]>
(cherry picked from commit 4830ea1)

* Connect: Generate json schema for app config (#22538)

* Rename `putAllSync` to `writeSync`

* Extend `FileStorage` with functions to replace an entire state and return file path used to create it

* Add `updateJsonSchema` function

* Generate schema for the app config

* Always return a string from `createMockFileStorage().getFilePath()`

* Add missing license headers

* Rename 'teleport_connect_config_schema.json' to 'schema_app_config.json'

* Rename `getKeyboardShortcutDescription` to `getShortcutDesc`

* Rename `keyboardShortcutSchema` to `shortcutSchema`

* Update a valid shortcut message

* Rename `configJsonSchemaFile` to `jsonSchema`

* Simplify `updateJsonSchema`

* Set `$refStrategy` to none

* Add missing description for `usageReporting.enabled`

* Bump zod to the latest (improves TS performance)

* Move `configService` implementation to `configStore`

* Rename `configStore` to `configService`

* Move `updateJsonSchema` to `createConfigService`

* Move `validateStoredConfig` outside `createConfigService`

* Rename `createAppConfigSchema.ts` to `appConfigSchema.ts`, `getKeyboardShortcutSchema.ts` to `keyboardShortcutSchema.ts`

* Export `createKeyboardShortcutSchema`

* Add license header

(cherry picked from commit 9967149)

* Connect: Show config file errors (#22728)

* Add a function that returns error that might happen during loading the file

* Return validation and file loading errors from `ConfigService`

* Discard file storage updates when the file was not loaded correctly

* Do not show usage reporting dialog when the file was not loaded correctly

* Make title of notifications a little bit smaller, so longer titles do not look weird

* Get rid of sync fs functions wherever possible in file storage

* Move error handling code to `createFileStorage`

* Improve `getConfigError` comment

* Rename `discardUpdatesWhenLoadingFileFailed` to `discardUpdatesOnLoadError`

* Fix the error message in `notifyAboutConfigErrors`

* Revert "Make title of notifications a little bit smaller, so longer titles do not look weird"

* Make `writeSync` async too

* Return `unknown` from `FileStorage.get`

* Add a TODO comment about createFileStorage type

* Add "the" to "Using default config instead"

* Remove `toString()`

---------

Co-authored-by: Rafał Cieślak <[email protected]>

(cherry picked from commit 17ff6d6)

* Connect: Add "Open Config File" item to menu (#22730)

* Expose `openConfigFile` IPC

* Add "Open Config File" to `NavigationMenu`

* Make `NavigationMenu` more concise

* Remove unused imports

* Call "three dots" menu "More Options" to make it easier to refer to in the docs

* Show a notification with a config file path

* Render the icon component only in `NavigationItem`

* Render the separator only with access request items

* Use just `separator` string instead of an object

* Render `StyledListItem` as a button

* Shorten the notification

Co-authored-by: Rafał Cieślak <[email protected]>

---------

Co-authored-by: Rafał Cieślak <[email protected]>
(cherry picked from commit af9e0ed)

* Add docs for Connect config  (#22898)

* Rename old "quick input" to the current "command bar" feature name

* Remove 'Droid Sans Fallback' from Linux fonts

* Add docs for config

* Link to the proper documentation section

* Apply suggestions from @ravicious and @ibeckermayer

Co-authored-by: Rafał Cieślak <[email protected]>
Co-authored-by: Isaiah Becker-Mayer <[email protected]>

* Adjust property descriptions with the names used in User Interface

* Drop `Courier New`

* Simplify `$schema` description

* Fix lint issues

* Add a break line after each heading

Co-authored-by: Paul Gottschling <[email protected]>

* Schema should not be modified

* Config file is created on first launch

---------

Co-authored-by: Rafał Cieślak <[email protected]>
Co-authored-by: Isaiah Becker-Mayer <[email protected]>
Co-authored-by: Paul Gottschling <[email protected]>
(cherry picked from commit 2d387e1)
@gzdunek gzdunek deleted the gzdunek/generate-json-schema branch March 20, 2023 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants