|
@@ -10,6 +10,8 @@ import {t} from 'sentry/locale';
|
|
|
import type {Choice} from 'sentry/types/core';
|
|
|
import {defined} from 'sentry/utils';
|
|
|
|
|
|
+export const fieldIsRequiredMessage = t('Field is required');
|
|
|
+
|
|
|
type Snapshot = Map<string, FieldValue>;
|
|
|
type SaveSnapshot = (() => number) | null;
|
|
|
|
|
@@ -135,11 +137,13 @@ class FormModel {
|
|
|
fieldState: observable,
|
|
|
formState: observable,
|
|
|
|
|
|
+ isFormIncomplete: computed,
|
|
|
isError: computed,
|
|
|
isSaving: computed,
|
|
|
formData: computed,
|
|
|
formChanged: computed,
|
|
|
|
|
|
+ validateFormCompletion: action,
|
|
|
resetForm: action,
|
|
|
setFieldDescriptor: action,
|
|
|
removeField: action,
|
|
@@ -200,6 +204,13 @@ class FormModel {
|
|
|
return this.fields;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Are all required fields filled out
|
|
|
+ */
|
|
|
+ get isFormIncomplete() {
|
|
|
+ return this.formState === FormState.INCOMPLETE;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Is form saving
|
|
|
*/
|
|
@@ -240,7 +251,7 @@ class FormModel {
|
|
|
// TODO(TS): add type to props
|
|
|
this.fieldDescriptor.set(id, props);
|
|
|
|
|
|
- // Set default value iff initialData for field is undefined
|
|
|
+ // Set default value if initialData for field is undefined
|
|
|
// This must take place before checking for `props.setValue` so that it can
|
|
|
// be applied to `defaultValue`
|
|
|
if (
|
|
@@ -259,6 +270,8 @@ class FormModel {
|
|
|
this.initialData[id] = props.setValue(this.initialData[id], props);
|
|
|
this.fields.set(id, this.initialData[id]);
|
|
|
}
|
|
|
+
|
|
|
+ this.validateFormCompletion();
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -348,14 +361,28 @@ class FormModel {
|
|
|
return this.errors.has(id) && this.errors.get(id);
|
|
|
}
|
|
|
|
|
|
+ getErrors() {
|
|
|
+ return this.errors;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Returns true if not required or is required and is not empty
|
|
|
*/
|
|
|
isValidRequiredField(id: string) {
|
|
|
// Check field descriptor to see if field is required
|
|
|
const isRequired = this.getDescriptor(id, 'required');
|
|
|
+
|
|
|
+ if (!isRequired) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
const value = this.getValue(id);
|
|
|
- return !isRequired || (value !== '' && defined(value));
|
|
|
+
|
|
|
+ if (Array.isArray(value)) {
|
|
|
+ return value.length > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return value !== '' && defined(value);
|
|
|
}
|
|
|
|
|
|
isValidField(id: string) {
|
|
@@ -416,8 +443,6 @@ class FormModel {
|
|
|
errors = validate({model: this, id, form: this.getData()}) || [];
|
|
|
}
|
|
|
|
|
|
- const fieldIsRequiredMessage = t('Field is required');
|
|
|
-
|
|
|
if (!this.isValidRequiredField(id)) {
|
|
|
errors.push([id, fieldIsRequiredMessage]);
|
|
|
}
|
|
@@ -722,7 +747,7 @@ class FormModel {
|
|
|
this.formState = FormState.ERROR;
|
|
|
this.errors.set(id, error);
|
|
|
} else {
|
|
|
- this.formState = FormState.READY;
|
|
|
+ this.validateFormCompletion();
|
|
|
this.errors.delete(id);
|
|
|
}
|
|
|
|
|
@@ -739,6 +764,17 @@ class FormModel {
|
|
|
return !this.isError;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Validate if all required fields are filled out
|
|
|
+ */
|
|
|
+ validateFormCompletion() {
|
|
|
+ const formComplete = Array.from(this.fieldDescriptor.keys()).every(field =>
|
|
|
+ this.isValidRequiredField(field)
|
|
|
+ );
|
|
|
+
|
|
|
+ this.formState = !formComplete ? FormState.INCOMPLETE : FormState.READY;
|
|
|
+ }
|
|
|
+
|
|
|
handleErrorResponse({responseJSON: resp}: {responseJSON?: any} = {}) {
|
|
|
if (!resp) {
|
|
|
return;
|