-
Notifications
You must be signed in to change notification settings - Fork 595
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
Pro 6799 design default values invisible fields #4816
Conversation
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 like where you're going with this.
6de153c
to
9bbcd21
Compare
const errorsList = []; | ||
const isCurrentVisible = isParentVisible === false | ||
? false | ||
: await self.isVisible(req, schema, data, field.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.
Notable difference here (previous versions might be buggy). We pass data instead of destination because destination is not fully built at this point.
I think isVisible
should compare data as they have been set by the user and not the potential default values.
Tests are green with it but you might have some reasons to not allow this.
Let me know.
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.
Data can be unvalidated stuff of unexpected types etc., it could be anything. Trusting it is problematic. You might get away with that in isVisible
because you're not saving the values, but then isVisible
must be written carefully to not assume the values are at all well-formed, for instance it must not assume string fields are actually strings yet, etc. because nothing has been validated.
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.
data
might contains obsolete values.
You can also have preventFieldsOnly
set to true
meaning destination
will only contains partial fields.
We should think about these and if it has any impact on your implementation.
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 now uses destination when eveything as been converted.
handler() { | ||
this.updateNextAndEmit(); | ||
} | ||
}, |
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.
We don't need a deep watch here, we can simply call the method when v-model
updates fieldState
, see above change.
@@ -52,6 +52,7 @@ | |||
:server-error="fields[field.name].serverError" | |||
:doc-id="docId" | |||
:generation="generation" | |||
@update:model-value="updateNextAndEmit" |
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.
With this, no more need for a deep watcher.
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 not on the compare meta version?
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.
To keep the behavior consistent, fieldState
was watched before, I just replaced the watch by this. compareMetaState
wasn't.
} else { | ||
this.next.data[field.name] = this.modelValue.data[field.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.
Only indentation in this block
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 is possible that changes are not necessary but see my note here about trusting data
, isVisible
must be written very defensively if it is going to see unvalidated user input.
const errorsList = []; | ||
const isCurrentVisible = isParentVisible === false | ||
? false | ||
: await self.isVisible(req, schema, data, field.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.
Data can be unvalidated stuff of unexpected types etc., it could be anything. Trusting it is problematic. You might get away with that in isVisible
because you're not saving the values, but then isVisible
must be written carefully to not assume the values are at all well-formed, for instance it must not assume string fields are actually strings yet, etc. because nothing has been validated.
@@ -52,6 +52,7 @@ | |||
:server-error="fields[field.name].serverError" | |||
:doc-id="docId" | |||
:generation="generation" | |||
@update:model-value="updateNextAndEmit" |
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 not on the compare meta version?
const errorsList = []; | ||
const isCurrentVisible = isParentVisible === false | ||
? false | ||
: await self.isVisible(req, schema, data, field.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.
data
might contains obsolete values.
You can also have preventFieldsOnly
set to true
meaning destination
will only contains partial fields.
We should think about these and if it has any impact on your implementation.
52e6290
to
2341d53
Compare
} | ||
|
||
if (typeof error !== 'string') { | ||
self.apos.util.error(error.path + '\n' + error.stack); |
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.
Makes sense to me to show what field failed, error path being the 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.
I think it is better to log this detail later when logging the overall error. But I wouldn't die on that hill.
Will the error type ever be a string? I believe that went away in A3/A4...
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 guess it's smart to check.
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.
Idk, this was existing code, I removed it and moved the logs out of handleErrors
function.
Also created a ticket to improve logs in convert.
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 like the general strategy. Still think it makes more sense to set aposPath
on each field
just once in schema.validate
and its related functions.
CHANGELOG.md
Outdated
@@ -7,6 +7,8 @@ | |||
* Focus properly Widget Editor modals when opened. Keep the previous active focus on the modal when closing the widget editor. | |||
* a11y improvements for context menus. | |||
* Fixes broken widget preview URL when the image is overridden (module improve) and external build module is registered. | |||
* In th schema convert method, we wait all sub convert to run, to have access to the final destination object in order to check if |
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.
typo
} = {} | ||
) { | ||
const options = { | ||
fetchRelationships, | ||
ancestors, | ||
isParentVisible | ||
ancestorSchemas, | ||
ancestorPath |
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 think it is better to compute the aposPath
of a field once, in the validate
method of schema
and its related functions, rather than pushing this boulder up the hill on each convert call 🪨
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.
Yes good point. We agree that we are talking about the schema path and not the actual destination one.
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.
Done, it's now computed during validate
.
} | ||
|
||
if (typeof error !== 'string') { | ||
self.apos.util.error(error.path + '\n' + error.stack); |
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 think it is better to log this detail later when logging the overall error. But I wouldn't die on that hill.
Will the error type ever be a string? I believe that went away in A3/A4...
} | ||
|
||
if (typeof error !== 'string') { | ||
self.apos.util.error(error.path + '\n' + error.stack); |
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 guess it's smart to check.
a4969e5
to
2601619
Compare
destination, | ||
conditionalFields) | ||
) | ||
); |
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 linting
|
||
for (const error of validErrors) { | ||
self.apos.util.error(error.stack); | ||
} |
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 log of errors out of handleErrors
method.
Created a ticket to improve globally message printed from convert errors.
Also, log stack only because it contains the error name and message also.
// We set default values only on final error fields | ||
if (nonVisibleField && !error.data?.errors) { | ||
const curSchema = self.getFieldLevelSchema(schema, error.schemaPath); | ||
self.setDefaultToInvisibleField(curDestination, curSchema, error.path); |
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'm not sure this method is really useful.
I wrote a test for it and realized I saw it work only for a string that has a pattern.
In the case of in integer having a min
or a max
for example, the convert updates the value itself, it does not need this method.
Maybe there are some cases I didn't test.
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.
If the field is not visible and never gets touched in the UI, doesn't the frontend submit def
anyway? It should.
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 don't know, maybe it does.
My question was more about in what cases this is useful except for invisible string fields having a pattern that is not respected in the existing data.
I'm asking you because this function simply wraps existing code from you.
Also because you evoke min and max in the comments but I realized the convert already fixed min and max doe invisible fields.
setDefaultToInvisibleField(destination, schema, fieldPath) {
// Field path might contain the ID of the object in which it is contained
// We just want the field name here
const [ _id, fieldName ] = fieldPath.includes('.')
? fieldPath.split('.')
: [ null, fieldPath ];
// It is not reasonable to enforce required,
// min, max or anything else for fields
// hidden via "if" as the user cannot correct it
// and it will not be used. If the user changes
// the conditional field later then they won't
// be able to save until the erroneous field
// is corrected
const field = schema.find(field => field.name === fieldName);
if (field) {
// To protect against security issues, an invalid value
// for a field that is not visible should be quietly discarded.
// We only worry about this if the value is not valid, as otherwise
// it's a kindness to save the work so the user can toggle back to it
destination[field.name] = klona((field.def !== undefined)
? field.def
: self.fieldTypes[field.type]?.def);
}
}
So I'm just wondering how useful is it, is it worth keeping it etc. Also how to properly test it.
// it's a kindness to save the work so the user can toggle back to it | ||
destination[field.name] = klona((field.def !== undefined) | ||
? field.def | ||
: self.fieldTypes[field.type]?.def); |
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.
As described above, not sure where this method is useful except for strings have a pattern
property.
field.if, | ||
destination, | ||
conditionalFields | ||
); |
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 linting
} | ||
}); | ||
}, | ||
|
||
// Validates a single schema field. See `validate`. | ||
validateField(field, options, parent = null) { | ||
field.aposPath = parent | ||
? `${parent.aposPath}/${field.name}` | ||
: field.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.
Build aposPath
by using parent aposPath
schema.forEach(field => { | ||
// Infinite recursion prevention | ||
const key = `${options.type}:${options.subtype}.${field.name}`; | ||
if (!self.validatedSchemas[key]) { | ||
self.validatedSchemas[key] = true; | ||
self.validateField(field, options); | ||
self.validateField(field, options, parent); |
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.
relationship
field uses validate
u an not validateField
like does array
and object
fields.
Maybe the three should use the validate method since we can now pass the parent?
...(destination[field.name] | ||
?.find?.(doc => doc._id === item._id) | ||
?._fields || {}) | ||
...destItem?._fields || {} |
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 make code more readable.
@@ -1218,7 +1219,7 @@ module.exports = (self) => { | |||
self.validate(_field.schema, { | |||
type: 'relationship', | |||
subtype: _field.withType | |||
}); | |||
}, _field); |
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.
Passing the parent to validate
. Should we modify object
and array
to use the same validate
method instead of validateField
. (the validate method keeps tracks of already validated fields). I think it would make sense to use the same method for relationship
, object
and array
.
…ontains ID of containing object
e3458a3
to
1508b0a
Compare
CHANGELOG.md
Outdated
@@ -5,6 +5,12 @@ | |||
### Fixes | |||
|
|||
* Fixes a bug where images in Media manager are not selectable (click on an image does nothing) in both default and relationship mode. | |||
* In the schema convert method, we wait all sub convert to run, to have access to the final destination object. In order to check if |
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.
How about: "eliminated superfluous error messages. The convert
method now waits for all recursive invocations to complete before attempting to determine if fields are visible."
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.
Done
CHANGELOG.md
Outdated
|
||
### Adds | ||
|
||
* Possibility to set a field not ready when performing async operations, when a field isn't ready, the validation and emit won't occur. |
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.
Fields can now be marked as "not ready" when performing async operations. When a field isn't ready, the validation and emit won't occur.
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.
Done
// We set default values only on final error fields | ||
if (nonVisibleField && !error.data?.errors) { | ||
const curSchema = self.getFieldLevelSchema(schema, error.schemaPath); | ||
self.setDefaultToInvisibleField(curDestination, curSchema, error.path); |
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.
If the field is not visible and never gets touched in the UI, doesn't the frontend submit def
anyway? It should.
PRO-6528
Summary
fieldReady
property for each field.Must work with async fields like dynamic choices, being able to set a field not ready during fetching and ready when operation is finished.
aposPath
to fields during validation process.What are the specific steps to test this change?
Cypress tests 🟢
What kind of change does this PR introduce?
Make sure the PR fulfills these requirements:
If adding a new feature without an already open issue, it's best to open a feature request issue first and wait for approval before working on it.
Other information: