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

New deployment forms: add json-schema-based table #5412

Merged
merged 29 commits into from Oct 5, 2022

Conversation

antgamdia
Copy link
Contributor

@antgamdia antgamdia commented Sep 29, 2022

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,

Additional information

Still a draft as some tests are still pending.

image

image

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>
@netlify
Copy link

netlify bot commented Sep 29, 2022

Deploy Preview for kubeapps-dev ready!

Name Link
🔨 Latest commit f48ec48
🔍 Latest deploy log https://app.netlify.com/sites/kubeapps-dev/deploys/633d64dea71cdf0009b75ae8
😎 Deploy Preview https://deploy-preview-5412--kubeapps-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site settings.

Copy link
Contributor

@github-actions github-actions bot left a 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

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>
Signed-off-by: Antonio Gamez Diaz <agamez@vmware.com>
@antgamdia
Copy link
Contributor Author

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>
Comment on lines +111 to +113
"react-dom": "^17.0.2",
"react-scripts": "^5.0.1",
"react-test-renderer": "^17.0.2",
Copy link
Contributor Author

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}>
Copy link
Contributor Author

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) => {
Copy link
Contributor Author

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({
Copy link
Contributor Author

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 = () => {
Copy link
Contributor Author

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);
Copy link
Contributor Author

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);
Copy link
Contributor Author

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> & {
Copy link
Contributor Author

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(
Copy link
Contributor Author

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

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've extracted plenty of the logic formerly present on the "schema.ts" file here, just to separate their concerns.

@antgamdia antgamdia marked this pull request as ready for review October 3, 2022 12:29
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>
Comment on lines +237 to +245
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}`,
);
}
Copy link
Contributor Author

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}
Copy link
Contributor Author

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}
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly, that's why I left the hacky ref, otherwise the HTML are not taken into account :(

nohtmlval

isCustomComponent?: boolean;
};

export interface IAjvValidateResult {
Copy link
Collaborator

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? 😅

Copy link
Contributor Author

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

Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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);
Copy link
Collaborator

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.

Copy link
Contributor Author

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? 😅

Copy link
Collaborator

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.

Copy link
Contributor Author

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 :(

Copy link
Collaborator

@castelblanque castelblanque left a 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment