diff --git a/client-src/css/forms-css.js b/client-src/css/forms-css.js
index 2069fd9aaf92..956ace7bebc4 100644
--- a/client-src/css/forms-css.js
+++ b/client-src/css/forms-css.js
@@ -78,6 +78,19 @@ export const FORM_STYLES = [
margin-top: 2em;
}
+ .fade-in {
+ animation:fadeIn 0.5s linear;
+ }
+
+ @keyframes fadeIn {
+ 0% {
+ opacity:0
+ }
+ 100% {
+ opacity:1;
+ }
+ }
+
#metadata, .stage_form, .flat_form, .final_buttons {
max-width: 67em;
padding: 1em;
diff --git a/client-src/elements/chromedash-form-field.js b/client-src/elements/chromedash-form-field.js
index c41898c92374..8e66093b5f80 100644
--- a/client-src/elements/chromedash-form-field.js
+++ b/client-src/elements/chromedash-form-field.js
@@ -13,6 +13,7 @@ export class ChromedashFormField extends LitElement {
index: {type: Number}, // Represents which field this is on the form.
disabled: {type: Boolean},
checkboxLabel: {type: String}, // Optional override of default label.
+ shouldFadeIn: {type: Boolean},
loading: {type: Boolean},
fieldProps: {type: Object},
forEnterprise: {type: Boolean},
@@ -28,6 +29,7 @@ export class ChromedashFormField extends LitElement {
this.index = -1;
this.checkboxLabel = '';
this.disabled = false;
+ this.shouldFadeIn = false;
this.loading = false;
this.forEnterprise = false;
this.stageType = undefined;
@@ -234,15 +236,16 @@ export class ChromedashFormField extends LitElement {
this.forEnterprise && (this.fieldProps.enterprise_extra_help !== undefined) ?
this.fieldProps.enterprise_extra_help :
this.fieldProps.extra_help;
+ const fadeInClass = (this.shouldFadeIn) ? 'fade-in' : '';
return html`
${this.fieldProps.label ? html`
-
${this.renderWidgets()} |
${helpText ? html` ${helpText} `: nothing}
diff --git a/client-src/elements/chromedash-ot-creation-page.js b/client-src/elements/chromedash-ot-creation-page.js
index 0a74c2741a33..a540b0d2934e 100644
--- a/client-src/elements/chromedash-ot-creation-page.js
+++ b/client-src/elements/chromedash-ot-creation-page.js
@@ -1,4 +1,4 @@
-import {LitElement, css, html} from 'lit';
+import {LitElement, css, html, nothing} from 'lit';
import {ref} from 'lit/directives/ref.js';
import {
formatFeatureChanges,
@@ -10,6 +10,7 @@ import './chromedash-form-field.js';
import {ORIGIN_TRIAL_CREATION_FIELDS} from './form-definition.js';
import {SHARED_STYLES} from '../css/shared-css.js';
import {FORM_STYLES} from '../css/forms-css.js';
+import {ALL_FIELDS} from './form-field-specs.js';
export class ChromedashOTCreationPage extends LitElement {
@@ -31,6 +32,7 @@ export class ChromedashOTCreationPage extends LitElement {
appTitle: {type: String},
nextPage: {type: String},
fieldValues: {type: Array},
+ showApprovalsFields: {type: Boolean},
};
}
@@ -43,6 +45,7 @@ export class ChromedashOTCreationPage extends LitElement {
this.appTitle = '';
this.nextPage = '';
this.fieldValues = [];
+ this.showApprovalsFields = false;
}
connectedCallback() {
@@ -61,6 +64,11 @@ export class ChromedashOTCreationPage extends LitElement {
// The field has been updated, so it is considered touched.
this.fieldValues[index].touched = true;
this.fieldValues[index].value = value;
+
+ if (this.fieldValues[index].name === 'ot_require_approvals') {
+ this.showApprovalsFields = !this.showApprovalsFields;
+ this.requestUpdate();
+ }
};
fetchData() {
@@ -75,12 +83,78 @@ export class ChromedashOTCreationPage extends LitElement {
if (this.feature.name) {
document.title = `${this.feature.name} - ${this.appTitle}`;
}
+ this.setFieldValues();
this.loading = false;
- }).catch(() => {
- showToastMessage('Some errors occurred. Please refresh the page or try again later.');
});
}
+ addOptionalApprovalsFields() {
+ // Approval requirement fields are always hidden unless the feature owner
+ // opts in to using them.
+ const insertIndex = this.fieldValues.findIndex(
+ fieldInfo => fieldInfo.name === 'ot_require_approvals') + 1;
+ this.fieldValues.splice(insertIndex, 0,
+ {
+ name: 'ot_approval_buganizer_component',
+ touched: true,
+ value: '',
+ stageId: this.stage.id,
+ isApprovalsField: true,
+ },
+ {
+ name: 'ot_approval_group_email',
+ touched: true,
+ value: '',
+ stageId: this.stage.id,
+ isApprovalsField: true,
+ },
+ {
+ name: 'ot_approval_criteria_url',
+ touched: true,
+ value: '',
+ stageId: this.stage.id,
+ isApprovalsField: true,
+ });
+ }
+
+ addHiddenFields() {
+ // Add a field for updating that an OT creation request has been submitted.
+ this.fieldValues.push({
+ name: 'ot_action_requested',
+ touched: true,
+ value: true,
+ stageId: this.stage.id,
+ alwaysHidden: true,
+ });
+ }
+
+ setFieldValues() {
+ // OT creation page only has one section.
+ const section = ORIGIN_TRIAL_CREATION_FIELDS.sections[0];
+
+ this.fieldValues = section.fields.map(field => {
+ const featureJSONKey = ALL_FIELDS[field].name || field;
+ let value = getStageValue(this.stage, featureJSONKey);
+ let touched = false;
+
+ // The requester's email should be a contact by default.
+ if (featureJSONKey === 'ot_owner_email' && !value) {
+ value = [this.userEmail];
+ touched = true;
+ }
+
+ // Add the field to this component's stage before creating the field component.
+ return {
+ name: featureJSONKey,
+ touched,
+ value,
+ stageId: this.stage.id,
+ };
+ });
+ this.addOptionalApprovalsFields();
+ this.addHiddenFields();
+ }
+
disconnectedCallback() {
super.disconnectedCallback();
document.title = this.appTitle;
@@ -111,8 +185,6 @@ export class ChromedashOTCreationPage extends LitElement {
setTimeout(() => {
window.location.href = this.nextPage || `/feature/${this.featureId}`;
}, 1000);
- }).catch(() => {
- showToastMessage('Some errors occurred. Please refresh the page or try again later.');
});
}
@@ -156,41 +228,24 @@ export class ChromedashOTCreationPage extends LitElement {
`;
}
- renderFields(section) {
- const fields = section.fields.map(field => {
- let value = getStageValue(this.stage, field);
- let touched = false;
- // The requester's email should be a contact by default.
- if (field === 'ot_owner_email' && !value) {
- value = [this.userEmail];
- touched = true;
+ renderFields() {
+ const fields = this.fieldValues.map((fieldInfo, i) => {
+ if (fieldInfo.alwaysHidden || (fieldInfo.isApprovalsField && !this.showApprovalsFields)) {
+ return nothing;
}
- // Add the field to this component's stage before creating the field component.
- const index = this.fieldValues.length;
- this.fieldValues.push({
- name: field,
- touched,
- value,
- stageId: this.stage.id,
- });
+ // Fade in transition for the approvals fields if they're being displayed.
+ const shouldFadeIn = fieldInfo.isApprovalsField;
return html`
`;
});
-
- // Add a field for updating that an OT creation request has been submitted.
- this.fieldValues.push({
- name: 'ot_action_requested',
- touched: true,
- value: true,
- stageId: this.stage.id,
- });
return fields;
}
diff --git a/client-src/elements/form-definition.js b/client-src/elements/form-definition.js
index b53d3a160a52..af210de059a9 100644
--- a/client-src/elements/form-definition.js
+++ b/client-src/elements/form-definition.js
@@ -527,6 +527,7 @@ export const ORIGIN_TRIAL_CREATION_FIELDS = {
'ot_is_deprecation_trial',
'ot_has_third_party_support',
'ot_is_critical_trial',
+ 'ot_require_approvals',
'ot_request_note',
],
},
diff --git a/client-src/elements/form-field-enums.js b/client-src/elements/form-field-enums.js
index 50e66367e605..19f4a3e37cb2 100644
--- a/client-src/elements/form-field-enums.js
+++ b/client-src/elements/form-field-enums.js
@@ -261,6 +261,10 @@ export const STAGE_SPECIFIC_FIELDS = new Set([
'ot_creation__milestone_desktop_first',
'ot_creation__milestone_desktop_last',
'ot_extension__milestone_desktop_last',
+ 'ot_require_approvals',
+ 'ot_approval_buganizer_component',
+ 'ot_approval_group_email',
+ 'ot_approval_criteria_url',
'finch_url',
'experiment_goals',
'experiment_risks',
diff --git a/client-src/elements/form-field-specs.js b/client-src/elements/form-field-specs.js
index 3a3bf735d6d7..2f7068976de6 100644
--- a/client-src/elements/form-field-specs.js
+++ b/client-src/elements/form-field-specs.js
@@ -1128,6 +1128,46 @@ export const ALL_FIELDS = {
>web_feature.mojom.`,
},
+ 'ot_require_approvals': {
+ type: 'checkbox',
+ initial: false,
+ label: 'Trial participation requires approval',
+ help_text: html`
+ Will this trial require registrants to receive approval before
+ participating? See information about this setting at
+ Some link`,
+ },
+
+ 'ot_approval_buganizer_component': {
+ type: 'input',
+ attrs: {type: 'number'},
+ required: true,
+ label: 'Approvals Buganizer component ID',
+ help_text: html`
+ Buganizer component ID used for approvals requests.`,
+ },
+
+ 'ot_approval_group_email': {
+ type: 'input',
+ required: true,
+ attrs: {...TEXT_FIELD_ATTRS, pattern: GOOGLE_EMAIL_ADDRESS_REGEX},
+ label: 'Registration request notifications group',
+ help_text: html`
+
+ Google group email to be used for new registration request notifications.
+ Please supply a '@google.com' domain email address only.
+ `,
+ },
+
+ 'ot_approval_criteria_url': {
+ type: 'input',
+ attrs: URL_FIELD_ATTRS,
+ required: true,
+ label: 'Approval criteria link',
+ help_text: html`
+ Link to documentation describing the requirements to be approved for trial participation.`,
+ },
+
'ot_creation__intent_to_experiment_url': {
name: 'intent_to_experiment_url',
type: 'input',
|