-
Notifications
You must be signed in to change notification settings - Fork 707
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
New deployment forms: add json-schema-based table #5412
Conversation
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
✅ Deploy Preview for kubeapps-dev ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
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.
license-eye has totally checked 1414 files.
Valid | Invalid | Ignored | Fixed |
---|---|---|---|
740 | 1 | 673 | 0 |
Click to see the invalid file list
- dashboard/src/components/DeploymentForm/DeploymentFormBody/BasicDeploymentForm/TabularSchemaEditorTable/TabularSchemaEditorTableRenderer.tsx
...ntFormBody/BasicDeploymentForm/TabularSchemaEditorTable/TabularSchemaEditorTableRenderer.tsx
Show resolved
Hide resolved
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
492bcf2
to
37e3a6f
Compare
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
CI passed 🎉 Marking as ready for review + adding in-line comments to the code. |
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
"react-dom": "^17.0.2", | ||
"react-scripts": "^5.0.1", | ||
"react-test-renderer": "^17.0.2", |
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.
Moved to dev-deps as they are not required in the production build
@@ -209,7 +209,7 @@ export default function DeploymentForm() { | |||
</Column> | |||
<Column span={9}> | |||
{error && <Alert theme="danger">An error occurred: {error.message}</Alert>} | |||
<form onSubmit={handleDeploy}> | |||
<form onSubmit={handleDeploy} ref={formRef}> |
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 is a hacky way to pass information from the children to the parent container in React. We need to manually control the form submission downstream.
valuesFromTheParentContainer, | ||
]); | ||
|
||
const editorDidMount = (editor: monaco.editor.IStandaloneDiffEditor, m: typeof monaco) => { |
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.
Here we are just creating some commands in the palette and context menu (when you right-click on the editor).
import { CdsInput } from "@cds/react/input"; | ||
import { InputHTMLAttributes, useEffect, useState } from "react"; | ||
|
||
export default function DebouncedInput({ |
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 component was mainly extracted from the Table documentation; in the future, we can think of reusing the same for other inputs (like the catalog search)
} | ||
}; | ||
|
||
const onAddArrayItem = () => { |
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.
Init the array value with some value when clicking on "+"
{} as YAML.Document.Parsed<YAML.ParsedNode>, | ||
); | ||
const [restoreModalIsOpen, setRestoreModalOpen] = useState(false); | ||
const [isLoaded, setIsloaded] = useState(false); |
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.
Handles the very first component render
); | ||
const [restoreModalIsOpen, setRestoreModalOpen] = useState(false); | ||
const [isLoaded, setIsloaded] = useState(false); | ||
const [isLoading, setIsloading] = useState(true); |
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.
Handles each loading time when the YAML is being parsed back into nodes (eg, when the text editor is changed)
customComponent?: object; | ||
// We extend the JSONSchema properties to include the default/deployed values as well as | ||
// other useful information for rendering each param in the UI | ||
export type IBasicFormParam = JSONSchemaType<any> & { |
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 way, we simply extend the existing JSONSchemaType
with some operational field we are gonna need for rendering the table easily.
|
||
// retrieveBasicFormParams iterates over a JSON Schema properties looking for `form` keys | ||
// It uses the raw yaml to setup default values. | ||
// It returns a key:value map for easier handling. | ||
export function retrieveBasicFormParams( |
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 is the core function for, given a jsonschema + values file, creating the array of nested params. The logic is simple: if this is a property without children, extract everything valuable and add it to the results. Otherwise, continue exploring.
@@ -0,0 +1,128 @@ | |||
// Copyright 2019-2022 the Kubeapps contributors. | |||
// SPDX-License-Identifier: Apache-2.0 | |||
|
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.
I've extracted plenty of the logic formerly present on the "schema.ts" file here, just to separate their concerns.
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
const schemaValidation = validateSchema(schema); | ||
if (!schemaValidation.valid) { | ||
const errorText = schemaValidation?.errors | ||
?.map(e => ` - ${e.instancePath}: ${e.message}`) | ||
.join("\n"); | ||
throw new UnprocessableEntityError( | ||
`The schema for this package is not valid. Please contact the package author. The following errors were found:\n${errorText}`, | ||
); | ||
} |
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.
Validating the schema before actually creating the InstalledPackage. I have considered adding a "force button", however, Helm is also failing if a wrong schema is added (as it is validating against the chart.values.schema.json
if present in the tar.gz). A "force" button would imply some extra backend work.
Currently, for us, the problem is mainly just bitnami/readme-generator-for-helm#34.
<> | ||
<CdsInput className="self-center"> | ||
<input | ||
required={param.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.
Adding this basic HTML-based validation for solving this issue: #1924
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
@@ -267,6 +267,7 @@ export default function DeploymentForm() { | |||
setValues={handleValuesChange} | |||
appValues={appValues} | |||
setValuesModified={setValuesModifiedTrue} | |||
formRef={formRef} |
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.
It seems that formRef
is only used for performing a requestSubmit()
in the parent.
Why not passing a plain callback function as parameter instead of this reference?
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.
It was introduced in one of my previous attempts when working on this. The idea is to trigger a plain normal HTML form "submit" event from a child, this way we leverage the built-in HTML validations (like required
, max
, min
, pattern
).
Anyway, just to double-check, I'm trying locally to replace the ref
with the handleDeploy()
function directly to ensure the ref
is really needed or not.
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.
isCustomComponent?: boolean; | ||
}; | ||
|
||
export interface IAjvValidateResult { |
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.
Why using this "cryptic"" name? 😅
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.
No strong opinion on the naming, but since it is the result of performing the validate
function on anajv
(the library for handling schemas) object, this name is the best I could think of. Suggestions? :P
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.
Just mentioned that because reading it for the first time sounds difficult to identify what is the purpose.
Not important, though. What about ISchemaValidationResult
or similar? all in all, the types used inside the interface are not from ajv
. If we switch to another library in the future, one less thing we need to touch.
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.
Well, ErrorObject
, one of the used types, is indeed imported from the Ajv library, so we would be kinda coupled to ajv
anyway? But yep, at least we should add a comment explaining what is this interface for.
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.
You are right. Fine leaving it as is with a comment.
await page.click('li:has-text("Changes")'); | ||
|
||
// Wait until changes are applied (due to the debounce in the input) | ||
await page.waitForTimeout(1000); |
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.
Using waitForTimeout
is always a potential source of problems.
When having problems or debugging, it gives a bit more information to use something like
const locator = page.locator("input[type='number']")
await expect(locator).toHaveValue('2', {timeout: 1000})
BTW is there only one input of type number for this chart? Selector is very generic.
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.
100% agree on the selector, will change it to be more specific.
wrt the waitForTimeout
, I also agree, but I'm not sure the approach you mention worked in this case. Let me explain: once the user types something in an input, the actual HTML event will be only triggered after 500ms. So, unless you wait and stay in this very same component, the event won't get triggered. As a result, the diff view will always be empty, as there are no actual changes.
However, note that the <input ...
locator will get the value 2
immediately, as the actual HTML value is not denounced, just the onChange
event.
Therefore, I can't think of anything different but an actual active wait before continuing the rest of the interactions. In fact, I added two 1s-waits just for security, but a single one of 500ms right after the change should be enough.
Any suggestions? 😅
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.
Ok, so it is the event that we are waiting here.
Could we add more info in the comment to specify that it is the event please?
Not sure if Playwright's waitForEvent
+ predicate func will help 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.
Thanks for the tip, I've been exploring it, but unfortunately, the events it listens to are not HTML events, but some playwright-specific ones: backgroundpage
, close
, page
, request
, requestfailed
, requestfinished
, response
, serviceworfker
In SO, some responses point to more sophisticated alternatives (ideally for more denounced inputs). Example: https://stackoverflow.com/questions/73739729/how-to-test-expected-delay-time-on-page-element-fixed-wait-debounce-with-play
So, for now, I think we should keep it simple, and leave the wait :(
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.
Awesome work. Big step in making Kubeapps more generic and sustainable, thanks!
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
Description of the change
This PR is finally adding the changes related to the new JSON Schema-based table that aims to replace the old "basic form". This way, we can render a large amount of params with no lag and, what's more important: we get rid of the hacky ways to display a form using the
form: true
property in the schema.I'll comment on some changes in-line to make the review process easier once the PR is ready.
Benefits
Kubeapps will be able to render any JSONSchema-compliant schema. The overall performance of the form view gets boosted as well.
Possible drawbacks
Custom components: we need to re-check with our adopters the current situation.
Applicable issues
Additionally,
step
in both the slider and the input here)required
prop is now being added if present in the schema)Additional information
Still a draft as some tests are still pending.