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

Frontend changes for OT registration approvals #3444

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions client-src/css/forms-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions client-src/elements/chromedash-form-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class ChromedashFormField extends LitElement {
fieldValues: {type: Array}, // All other field value objects
disabled: {type: Boolean},
checkboxLabel: {type: String}, // Optional override of default label.
shouldFadeIn: {type: Boolean},
loading: {type: Boolean},
fieldProps: {type: Object},
forEnterprise: {type: Boolean},
Expand All @@ -31,6 +32,7 @@ export class ChromedashFormField extends LitElement {
this.fieldValues = [];
this.checkboxLabel = '';
this.disabled = false;
this.shouldFadeIn = false;
this.loading = false;
this.forEnterprise = false;
this.stageType = undefined;
Expand Down Expand Up @@ -296,15 +298,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`
<tr>
<tr class="${fadeInClass}">
<th colspan="2">
<b>${this.fieldProps.label}:</b>
</th>
</tr>
`: nothing}
<tr>
<tr class=${fadeInClass}>
<td>
${this.renderWidget()}
${this.checkMessage}
Expand Down
134 changes: 102 additions & 32 deletions client-src/elements/chromedash-ot-creation-page.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand All @@ -31,6 +32,7 @@ export class ChromedashOTCreationPage extends LitElement {
appTitle: {type: String},
nextPage: {type: String},
fieldValues: {type: Array},
showApprovalsFields: {type: Boolean},
};
}

Expand All @@ -43,6 +45,7 @@ export class ChromedashOTCreationPage extends LitElement {
this.appTitle = '';
this.nextPage = '';
this.fieldValues = [];
this.showApprovalsFields = false;
}

connectedCallback() {
Expand All @@ -61,6 +64,12 @@ 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;

// Toggle the display of the registration approval fields when the box is checked.
if (this.fieldValues[index].name === 'ot_require_approvals') {
this.showApprovalsFields = !this.showApprovalsFields;
this.requestUpdate();
}
};

fetchData() {
Expand All @@ -75,12 +84,83 @@ 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.');
});
}

// Add the togglable registration approvals fields to the fieldValues array.
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: false,
value: '',
stageId: this.stage.id,
isApprovalsField: true,
},
{
name: 'ot_approval_group_email',
touched: false,
value: '',
stageId: this.stage.id,
isApprovalsField: true,
},
{
name: 'ot_approval_criteria_url',
touched: false,
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,
});
}

// Create the array that tracks the value of fields.
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;
// Display registration approvals fields by default if the value is checked already.
} else if (featureJSONKey === 'ot_require_approvals') {
this.showApprovalsFields = !!value;
}

// 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;
Expand All @@ -102,6 +182,15 @@ export class ChromedashOTCreationPage extends LitElement {

handleFormSubmit(e) {
e.preventDefault();
// If registration approvals is not enabled, ignore all fields related to that setting.
if (!this.showApprovalsFields) {
this.fieldValues.forEach(fieldInfo => {
if (fieldInfo.isApprovalsField) {
fieldInfo.touched = false;
}
});
}

const featureSubmitBody = formatFeatureChanges(this.fieldValues, this.featureId);
// We only need the single stage changes.
const stageSubmitBody = featureSubmitBody.stages[0];
Expand All @@ -111,8 +200,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.');
});
}

Expand Down Expand Up @@ -156,42 +243,25 @@ 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`
<chromedash-form-field
name=${field}
index=${index}
value=${value}
name=${fieldInfo.name}
value=${fieldInfo.value}
index=${i}
.fieldValues=${this.fieldValues}
.shouldFadeIn=${shouldFadeIn}
@form-field-update="${this.handleFormFieldUpdate}">
</chromedash-form-field>
`;
});

// 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;
}

Expand Down
1 change: 1 addition & 0 deletions client-src/elements/form-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
},
Expand Down
4 changes: 4 additions & 0 deletions client-src/elements/form-field-enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
49 changes: 49 additions & 0 deletions client-src/elements/form-field-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,55 @@ export const ALL_FIELDS = {
>web_feature.mojom</a>.`,
},

'ot_require_approvals': {
type: 'checkbox',
initial: false,
label: 'Trial participation requires approval',
help_text: html`
<p>
Will this trial require registrants to receive approval before
participating? Please reach out to [email protected]
beforehand to discuss options here.
</p>`,
},

'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,
placeholder: 'ex. "[email protected]"',
},
label: 'Registration request notifications group',
help_text: html`
<p>
Google group email to be used for new registration request notifications.
Please supply a '@google.com' domain email address only.
</p>`,
},

'ot_approval_criteria_url': {
type: 'input',
attrs: URL_FIELD_ATTRS,
required: true,
label: 'Approval criteria link',
help_text: html`
<p>
Link to public documentation describing the requirements
to be approved for trial participation.
</p>`,
},

'ot_creation__intent_to_experiment_url': {
name: 'intent_to_experiment_url',
type: 'input',
Expand Down
Loading