UI: Namespace validation update (#22820)

This commit is contained in:
Chelsea Shaw
2023-09-08 10:02:52 -05:00
committed by GitHub
parent 8f9bf0c623
commit 3ae94183bf
6 changed files with 61 additions and 33 deletions

View File

@@ -5,19 +5,23 @@
import Model, { attr } from '@ember-data/model'; import Model, { attr } from '@ember-data/model';
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes'; import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
import { withModelValidations } from 'vault/decorators/model-validations';
@withExpandedAttributes() @withExpandedAttributes()
export default class NamespaceModel extends Model { @withModelValidations({
@attr('string', { path: [
validationAttr: 'pathIsValid', { type: 'presence', message: `Path can't be blank.` },
invalidMessage: 'You have entered and invalid path please only include letters, numbers, -, ., and _.', { type: 'endsInSlash', message: `Path can't end in forward slash '/'.` },
{
type: 'containsWhiteSpace',
message: "Path can't contain whitespace.",
},
],
}) })
export default class NamespaceModel extends Model {
@attr('string')
path; path;
get pathIsValid() {
return this.path && this.path.match(/^[\w\d-.]+$/g);
}
get fields() { get fields() {
return ['path'].map((f) => this.allByKey[f]); return ['path'].map((f) => this.allByKey[f]);
} }

View File

@@ -15,6 +15,9 @@ export default Component.extend({
layout, layout,
flashMessages: service(), flashMessages: service(),
// internal validations
invalidFormAlert: '',
modelValidations: null,
// public API // public API
model: null, model: null,
successMessage: 'Saved!', successMessage: 'Saved!',
@@ -38,10 +41,25 @@ export default Component.extend({
// is the case, set this value to true // is the case, set this value to true
callOnSaveAfterRender: false, callOnSaveAfterRender: false,
checkModelValidity(model) {
if (model.validate) {
const { isValid, state, invalidFormMessage } = model.validate();
this.set('modelValidations', state);
this.set('invalidFormAlert', invalidFormMessage);
return isValid;
}
// no validations on model; return true
return true;
},
save: task( save: task(
waitFor(function* (model, options = { method: 'save' }) { waitFor(function* (model, options = { method: 'save' }) {
const { method } = options; const { method } = options;
const messageKey = method === 'save' ? 'successMessage' : 'deleteSuccessMessage'; const messageKey = method === 'save' ? 'successMessage' : 'deleteSuccessMessage';
if (method === 'save' && !this.checkModelValidity(model)) {
// if saving and model invalid, don't continue
return;
}
try { try {
yield model[method](); yield model[method]();
} catch (err) { } catch (err) {

View File

@@ -281,12 +281,6 @@
class="input {{if this.validationError 'has-error-border'}}" class="input {{if this.validationError 'has-error-border'}}"
maxLength={{@attr.options.characterLimit}} maxLength={{@attr.options.characterLimit}}
/> />
{{! TODO: explore removing in favor of new model validations pattern since it is only used on the namespace model }}
{{#if @attr.options.validationAttr}}
{{#if (and (get @model this.valuePath) (not (get @model @attr.options.validationAttr)))}}
<AlertInline @type="danger" @message={{@attr.options.invalidMessage}} />
{{/if}}
{{/if}}
{{/if}} {{/if}}
</div> </div>
{{else if (or (eq @attr.type "boolean") (eq @attr.options.editType "boolean"))}} {{else if (or (eq @attr.type "boolean") (eq @attr.options.editType "boolean"))}}

View File

@@ -3,7 +3,7 @@
SPDX-License-Identifier: BUSL-1.1 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
<div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless {{if (eq @includeBox false) 'is-shadowless'}}"> <div class="field is-fullwidth box is-bottomless {{if (eq @includeBox false) 'is-shadowless'}}">
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<button <button
@@ -24,10 +24,11 @@
{{/if}} {{/if}}
{{#if @onCancel}} {{#if @onCancel}}
<div class="control"> <div class="control">
<button type="button" class="button" onclick={{action this.onCancel}} data-test-cancel-button> <button type="button" class="button" onclick={{action @onCancel}} data-test-cancel-button>
{{or @cancelButtonText "Cancel"}} {{or @cancelButtonText "Cancel"}}
</button> </button>
</div> </div>
{{/if}} {{/if}}
</div> </div>
{{yield to="error"}}
</div> </div>

View File

@@ -3,18 +3,19 @@
* SPDX-License-Identifier: BUSL-1.1 * SPDX-License-Identifier: BUSL-1.1
*/ */
import Component from '@ember/component'; import Component from '@glimmer/component';
import { computed } from '@ember/object';
import layout from '../templates/components/form-save-buttons';
/** /**
* @module FormSaveButtons * @module FormSaveButtons
* `FormSaveButtons` displays a button save and a cancel button at the bottom of a form. * `FormSaveButtons` displays a button save and a cancel button at the bottom of a form.
* To show an overall inline error message, use the :error yielded block like shown below.
* *
* @example * @example
* ```js * ```js
* <FormSaveButtons @saveButtonText="Save" @isSaving={{isSaving}} @cancelLinkParams={{array * <FormSaveButtons @saveButtonText="Save" @isSaving={{isSaving}} @cancelLinkParams={{array
* "foo.route"}} /> * "foo.route"}}>
* <:error>This is an error</:error>
* </FormSaveButtons>
* ``` * ```
* *
* @param [saveButtonText="Save" {String}] - The text that will be rendered on the Save button. * @param [saveButtonText="Save" {String}] - The text that will be rendered on the Save button.
@@ -26,13 +27,11 @@ import layout from '../templates/components/form-save-buttons';
* *
*/ */
export default Component.extend({ export default class FormSaveButtons extends Component {
layout, get cancelLink() {
tagName: '', const { cancelLinkParams } = this.args;
if (!Array.isArray(cancelLinkParams) || !cancelLinkParams.length) return null;
cancelLink: computed('cancelLinkParams.[]', function () { const [route, ...models] = cancelLinkParams;
if (!Array.isArray(this.cancelLinkParams) || !this.cancelLinkParams.length) return;
const [route, ...models] = this.cancelLinkParams;
return { route, models }; return { route, models };
}), }
}); }

View File

@@ -9,17 +9,29 @@
<NamespaceReminder @mode="save" /> <NamespaceReminder @mode="save" />
{{#if (or this.model.fields this.model.attrs)}} {{#if (or this.model.fields this.model.attrs)}}
{{#each (or this.model.fields this.model.attrs) as |attr|}} {{#each (or this.model.fields this.model.attrs) as |attr|}}
<FormField data-test-field @attr={{attr}} @model={{this.model}} @mode={{@mode}} /> <FormField
data-test-field
@attr={{attr}}
@model={{this.model}}
@mode={{@mode}}
@modelValidations={{this.modelValidations}}
/>
{{/each}} {{/each}}
{{else if this.model.fieldGroups}} {{else if this.model.fieldGroups}}
<FormFieldGroups @model={{this.model}} @mode={{@mode}} /> <FormFieldGroups @model={{this.model}} @mode={{@mode}} @modelValidations={{this.modelValidations}} />
{{/if}} {{/if}}
</div> </div>
<FormSaveButtons <FormSaveButtons
@isSaving={{this.save.isRunning}} @isSaving={{this.save.isRunning}}
@saveButtonText={{this.saveButtonText}} @saveButtonText={{this.saveButtonText}}
@canceLinkParams={{@canceLinkParams}} @cancelLinkParams={{@cancelLinkParams}}
@includeBox={{this.includeBox}} @includeBox={{this.includeBox}}
@onCancel={{@onCancel}} @onCancel={{@onCancel}}
/> >
<:error>
{{#if this.invalidFormAlert}}
<AlertInline @type="danger" @paddingTop={{true}} @message={{this.invalidFormAlert}} @mimicRefresh={{true}} />
{{/if}}
</:error>
</FormSaveButtons>
</form> </form>