Split timeline into numeric fields and separate impl fields (#1011)

* Partial progress for demo

* Added a separate card and POST handlers.

* Remove instructions about leaving because Guide UX prompts for webview milestone only when appropriate

* remove unused constant

* Fixed a spelling error

* Fixed copy-paste error for one field.
This commit is contained in:
Jason Robbins 2020-09-24 16:38:59 -07:00 коммит произвёл GitHub
Родитель 1e07806f5e
Коммит c17805fed1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 296 добавлений и 84 удалений

Просмотреть файл

@ -612,6 +612,22 @@ class FeatureHandler(common.ContentHandler):
ongoing_constraints=self.request.get('ongoing_constraints'),
)
if self.request.get('flag_name'):
feature.flag_name = self.request.get('flag_name')
if self.request.get('ot_milestone_desktop_start'):
feature.ot_milestone_desktop_start = int(self.request.get(
'ot_milestone_desktop_start'))
if self.request.get('ot_milestone_desktop_end'):
feature.ot_milestone_desktop_end = int(self.request.get(
'ot_milestone_desktop_end'))
if self.request.get('ot_milestone_android_start'):
feature.ot_milestone_android_start = int(self.request.get(
'ot_milestone_android_start'))
if self.request.get('ot_milestone_android_end'):
feature.ot_milestone_android_end = int(self.request.get(
'ot_milestone_android_end'))
params = []
if self.request.get('create_launch_bug') == 'on':
params.append(LAUNCH_PARAM)

Просмотреть файл

@ -80,6 +80,22 @@ STAGE_FORMS = {
},
}
IMPL_STATUS_FORMS = {
models.INTENT_EXPERIMENT:
(models.BEHIND_A_FLAG, guideforms.ImplStatus_DevTrial,),
models.INTENT_EXTEND_TRIAL:
(models.ORIGIN_TRIAL, guideforms.ImplStatus_OriginTrial,),
models.INTENT_IMPLEMENT_SHIP:
(None, guideforms.ImplStatus_AllMilestones,),
models.INTENT_SHIP:
(models.ENABLED_BY_DEFAULT, guideforms.ImplStatus_AllMilestones,),
models.INTENT_SHIPPED:
(models.ENABLED_BY_DEFAULT, guideforms.ImplStatus_AllMilestones,),
models.INTENT_REMOVED:
(models.REMOVED, guideforms.ImplStatus_AllMilestones,),
}
# Forms to be used on the "Edit all" page that shows a flat list of fields.
# [('Section name': form_class)].
FLAT_FORMS = [
@ -257,12 +273,29 @@ class FeatureEditStage(common.ContentHandler):
# TODO(jrobbins): show useful error if stage not found.
detail_form_class = STAGE_FORMS[f.feature_type][stage_id]
impl_status_offered, impl_status_form_class = IMPL_STATUS_FORMS.get(
stage_id, (None, None))
feature_edit_dict = f.format_for_edit()
detail_form = None
if detail_form_class:
detail_form = detail_form_class(feature_edit_dict)
impl_status_form = None
if impl_status_form_class:
impl_status_form = impl_status_form_class(feature_edit_dict)
# Provide new or populated form to template.
template_data.update({
'feature': f,
'feature_id': f.key().id,
'feature_form': detail_form_class(f.format_for_edit()),
'feature_form': detail_form,
'already_on_this_stage': stage_id == f.intent_stage,
'already_on_this_impl_status':
impl_status_offered == f.impl_status_chrome,
'impl_status_form': impl_status_form,
'impl_status_name': models.IMPLEMENTATION_STATUS.get(
impl_status_offered, None),
'impl_status_offered': impl_status_offered,
})
self._add_common_template_values(template_data)
@ -354,6 +387,23 @@ class FeatureEditStage(common.ContentHandler):
feature.shipped_opera_android_milestone = self.parse_int(
'shipped_opera_android_milestone')
if self.touched('ot_milestone_desktop_start'):
feature.ot_milestone_desktop_start = self.parse_int(
'ot_milestone_desktop_start')
if self.touched('ot_milestone_desktop_end'):
feature.ot_milestone_desktop_end = self.parse_int(
'ot_milestone_desktop_end')
if self.touched('ot_milestone_android_start'):
feature.ot_milestone_android_start = self.parse_int(
'ot_milestone_android_start')
if self.touched('ot_milestone_android_end'):
feature.ot_milestone_android_end = self.parse_int(
'ot_milestone_android_end')
if self.touched('flag_name'):
feature.flag_name = self.request.get('flag_name')
if self.touched('owner'):
feature.owner = self.split_emails('owner')
@ -377,6 +427,7 @@ class FeatureEditStage(common.ContentHandler):
if self.touched('feature_type'):
feature.feature_type = int(self.request.get('feature_type'))
# intent_stage can be be set either by <select> or a checkbox
if self.touched('intent_stage'):
feature.intent_stage = int(self.request.get('intent_stage'))
elif self.request.get('set_stage') == 'on':
@ -390,8 +441,13 @@ class FeatureEditStage(common.ContentHandler):
feature.summary = self.request.get('summary')
if self.touched('motivation'):
feature.motivation = self.request.get('motivation')
# impl_status_chrome can be be set either by <select> or a checkbox
if self.touched('impl_status_chrome'):
feature.impl_status_chrome = int(self.request.get('impl_status_chrome'))
elif self.request.get('set_impl_status') == 'on':
feature.impl_status_chrome = self.parse_int('impl_status_offered')
if self.touched('interop_compat_risks'):
feature.interop_compat_risks = self.request.get('interop_compat_risks')
if self.touched('ergonomics_risks'):

Просмотреть файл

@ -31,11 +31,6 @@ SHIPPED_HELP_TXT = (
'status to In development. If the flag is for an origin trial set status '
'to Origin trial.')
SHIPPED_WEBVIEW_HELP_TXT = (
'First milestone to ship with this status. Applies to Enabled by '
'default, Browser Intervention, and Deprecated.\n\n NOTE: for statuses '
'Behind a flag and Origin trial this MUST be blank.'
)
# We define all form fields here so that they can be include in one or more
# stage-specific fields without repeating the details and help text.
@ -331,8 +326,38 @@ ALL_FIELDS = {
# TODO(jrobbins): consider splitting this into start and end fields.
'experiment_timeline': forms.CharField(
label='Experiment Timeline', required=False,
widget=forms.Textarea(attrs={'rows': 2, 'cols': 50, 'maxlength': 1480}),
help_text='When does the experiment start and expire?'),
widget=forms.Textarea(attrs={
'rows': 2, 'cols': 50, 'maxlength': 1480,
'placeholder': 'This field is deprecated',
'disabled': 'disabled'}),
help_text=('When does the experiment start and expire? '
'Deprecated: '
'Please use the following numeric fields instead.')),
# TODO(jrobbins and jmedley): Refine help text.
'ot_milestone_desktop_start': forms.IntegerField(
required=False, label='OT desktop start',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('First desktop milestone that will support an origin '
'trial of this feature.')),
'ot_milestone_desktop_end': forms.IntegerField(
required=False, label='OT desktop end',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('Last desktop milestone that will support an origin '
'trial of this feature.')),
'ot_milestone_android_start': forms.IntegerField(
required=False, label='OT android start',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('First android milestone that will support an origin '
'trial of this feature.')),
'ot_milestone_android_end': forms.IntegerField(
required=False, label='OT android end',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('Last android milestone that will support an origin '
'trial of this feature.')),
'experiment_risks': forms.CharField(
label='Experiment Risks', required=False,
@ -486,28 +511,33 @@ ALL_FIELDS = {
help_text='Comma separated list of full email addresses.'),
'impl_status_chrome': forms.ChoiceField(
required=False, label='Status in Chromium',
choices=models.IMPLEMENTATION_STATUS.items()),
required=False, label='Implementatino status',
choices=models.IMPLEMENTATION_STATUS.items(),
help_text='Implementation status in Chromium'),
'shipped_milestone': forms.IntegerField(
required=False, label='',
required=False, label='Chrome for desktop',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text='Desktop:<br/>' + SHIPPED_HELP_TXT),
help_text=SHIPPED_HELP_TXT),
'shipped_android_milestone': forms.IntegerField(
required=False, label='',
required=False, label='Chrome for Android',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text='Chrome for Android:</br/>' + SHIPPED_HELP_TXT),
help_text=SHIPPED_HELP_TXT),
'shipped_ios_milestone': forms.IntegerField(
required=False, label='',
required=False, label='Chrome for iOS (RARE)',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text='Chrome for iOS (RARE):<br/>' + SHIPPED_HELP_TXT),
help_text=SHIPPED_HELP_TXT),
'shipped_webview_milestone': forms.IntegerField(
required=False, label='',
required=False, label='Android Webview',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text='Android WebView:<br/>' + SHIPPED_WEBVIEW_HELP_TXT),
help_text=SHIPPED_HELP_TXT),
'flag_name': forms.CharField(
label='Flag name', required=False,
help_text='Name of the flag that enables this feature.'),
'prefixed': forms.BooleanField(
required=False, initial=False, label='Prefixed?'),
@ -550,10 +580,14 @@ NewFeatureForm = define_form_class_using_shared_fields(
MetadataForm = define_form_class_using_shared_fields(
'MetadataForm',
('name', 'summary', 'unlisted', 'owner',
'blink_components', 'category',
'category',
'feature_type', 'intent_stage',
'search_tags',
'impl_status_chrome',
'blink_components',
'bug_url', 'launch_bug_url',
'impl_status_chrome', 'search_tags'))
))
@ -585,6 +619,12 @@ Any_DevTrial = define_form_class_using_shared_fields(
# TODO(jrobbins): api overview link
ImplStatus_DevTrial = define_form_class_using_shared_fields(
'ImplStatus_InDevTrial',
('shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'flag_name'))
NewFeature_EvalReadinessToShip = define_form_class_using_shared_fields(
'NewFeature_EvalReadinessToShip',
('doc_links', 'tag_review', 'spec_link', 'interop_compat_risks',
@ -592,41 +632,47 @@ NewFeature_EvalReadinessToShip = define_form_class_using_shared_fields(
'ff_views', 'ff_views_link', 'ff_views_notes',
'ie_views', 'ie_views_link', 'ie_views_notes',
'web_dev_views', 'web_dev_views_link', 'web_dev_views_notes',
'shipped_milestone', 'shipped_android_milestone', 'shipped_ios_milestone',
'shipped_webview_milestone', 'prefixed', 'comments'))
'prefixed', 'comments'))
ImplStatus_AllMilestones = define_form_class_using_shared_fields(
'ImplStatus_AllMilestones',
('shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'shipped_webview_milestone'))
NewFeature_OriginTrial = define_form_class_using_shared_fields(
'NewFeature_OriginTrial',
('experiment_goals', 'experiment_timeline', 'experiment_risks',
('experiment_goals', 'experiment_risks',
'experiment_extension_reason', 'ongoing_constraints',
'origin_trial_feedback_url', 'intent_to_experiment_url',
'i2e_lgtms', 'comments'))
ImplStatus_OriginTrial = define_form_class_using_shared_fields(
'ImplStatus_OriginTrial',
('experiment_timeline', # deprecated
'ot_milestone_desktop_start', 'ot_milestone_desktop_end',
'ot_milestone_android_start', 'ot_milestone_android_end'))
Most_PrepareToShip = define_form_class_using_shared_fields(
'Most_PrepareToShip',
('impl_status_chrome', 'shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'shipped_webview_milestone',
'tag_review', 'tag_review_status',
('tag_review', 'tag_review_status',
'intent_to_implement_url', 'origin_trial_feedback_url',
'launch_bug_url', 'intent_to_ship_url', 'i2s_lgtms', 'comments'))
PSA_PrepareToShip = define_form_class_using_shared_fields(
'PSA_PrepareToShip',
('impl_status_chrome', 'shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'shipped_webview_milestone',
'tag_review',
('tag_review',
'intent_to_implement_url', 'origin_trial_feedback_url',
'launch_bug_url', 'intent_to_ship_url', 'comments'))
Any_Ship = define_form_class_using_shared_fields(
'Any_Ship',
('impl_status_chrome', 'shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'shipped_webview_milestone',
'launch_bug_url', 'comments'))
('launch_bug_url', 'comments'))
Any_Identify = define_form_class_using_shared_fields(
@ -643,7 +689,7 @@ Any_Implement = define_form_class_using_shared_fields(
Existing_OriginTrial = define_form_class_using_shared_fields(
'Existing_OriginTrial',
('experiment_goals', 'experiment_timeline', 'experiment_risks',
('experiment_goals', 'experiment_risks',
'experiment_extension_reason', 'ongoing_constraints',
'intent_to_experiment_url', 'i2e_lgtms',
'origin_trial_feedback_url', 'comments'))
@ -682,26 +728,34 @@ Deprecation_Removed = define_form_class_using_shared_fields(
Flat_Metadata = define_form_class_using_shared_fields(
'Flat_Metadata',
('name', 'summary', 'unlisted', 'owner',
'blink_components', 'category',
(# Standardizaton
'name', 'summary', 'unlisted', 'owner',
'category',
'feature_type', 'intent_stage',
'search_tags',
# Implementtion
'impl_status_chrome',
'blink_components',
'bug_url', 'launch_bug_url',
'impl_status_chrome', 'search_tags', 'comments'))
'comments'))
Flat_Identify = define_form_class_using_shared_fields(
'Flat_Identify',
('motivation', 'initial_public_proposal_url', 'explainer_links'))
(# Standardization
'motivation', 'initial_public_proposal_url', 'explainer_links'))
Flat_Implement = define_form_class_using_shared_fields(
'Flat_Implement',
('spec_link', 'intent_to_implement_url'))
(# Standardization
'spec_link', 'intent_to_implement_url'))
Flat_DevTrial = define_form_class_using_shared_fields(
'Flat_DevTrial',
('doc_links',
(# Standardizaton
'doc_links',
'interop_compat_risks',
'safari_views', 'safari_views_link', 'safari_views_notes',
'ff_views', 'ff_views_link', 'ff_views_notes',
@ -710,25 +764,39 @@ Flat_DevTrial = define_form_class_using_shared_fields(
'security_review_status', 'privacy_review_status',
'ergonomics_risks', 'activation_risks', 'security_risks', 'debuggability',
'all_platforms', 'all_platforms_descr', 'wpt', 'wpt_descr',
'sample_links', 'devrel', 'ready_for_trial_url'))
'sample_links', 'devrel', 'ready_for_trial_url',
# TODO(jrobbins): UA support signals section
# Implementation
'flag_name'))
# TODO(jrobbins): api overview link
Flat_OriginTrial = define_form_class_using_shared_fields(
'Flat_OriginTrial',
('experiment_goals', 'experiment_timeline', 'experiment_risks',
(# Standardization
'experiment_goals',
'experiment_risks',
'experiment_extension_reason', 'ongoing_constraints',
'intent_to_experiment_url', 'i2e_lgtms',
'origin_trial_feedback_url'))
'origin_trial_feedback_url',
# Implementation
'experiment_timeline',
'ot_milestone_desktop_start', 'ot_milestone_desktop_end',
'ot_milestone_android_start', 'ot_milestone_android_end'))
Flat_PrepareToShip = define_form_class_using_shared_fields(
'Flat_PrepareToShip',
('tag_review', 'tag_review_status',
(# Standardization
'tag_review', 'tag_review_status',
'intent_to_ship_url', 'i2s_lgtms'))
Flat_Ship = define_form_class_using_shared_fields(
'Flat_Ship',
('shipped_milestone', 'shipped_android_milestone',
(# Implementation
'shipped_milestone', 'shipped_android_milestone',
'shipped_ios_milestone', 'shipped_webview_milestone'))

Просмотреть файл

@ -150,7 +150,7 @@ IMPLEMENTATION_STATUS = OrderedDict()
IMPLEMENTATION_STATUS[NO_ACTIVE_DEV] = 'No active development'
IMPLEMENTATION_STATUS[PROPOSED] = 'Proposed'
IMPLEMENTATION_STATUS[IN_DEVELOPMENT] = 'In development'
IMPLEMENTATION_STATUS[BEHIND_A_FLAG] = 'Behind a flag'
IMPLEMENTATION_STATUS[BEHIND_A_FLAG] = 'In developer trial (Behind a flag)'
IMPLEMENTATION_STATUS[ENABLED_BY_DEFAULT] = 'Enabled by default'
IMPLEMENTATION_STATUS[DEPRECATED] = 'Deprecated'
IMPLEMENTATION_STATUS[REMOVED] = 'Removed'
@ -1024,6 +1024,7 @@ class Feature(DictModel):
shipped_android_milestone = db.IntegerProperty()
shipped_ios_milestone = db.IntegerProperty()
shipped_webview_milestone = db.IntegerProperty()
flag_name = db.StringProperty()
owner = db.ListProperty(db.Email)
footprint = db.IntegerProperty() # Deprecated
@ -1079,6 +1080,10 @@ class Feature(DictModel):
experiment_goals = db.StringProperty(multiline=True)
experiment_timeline = db.StringProperty(multiline=True)
ot_milestone_desktop_start = db.IntegerProperty()
ot_milestone_desktop_end = db.IntegerProperty()
ot_milestone_android_start = db.IntegerProperty()
ot_milestone_android_end = db.IntegerProperty()
experiment_risks = db.StringProperty(multiline=True)
experiment_extension_reason = db.StringProperty(multiline=True)
ongoing_constraints = db.StringProperty(multiline=True)
@ -1120,15 +1125,15 @@ class PlaceholderCharField(forms.CharField):
class FeatureForm(forms.Form):
SHIPPED_HELP_TXT = ('First milestone to ship with this '
'status. Applies to: Enabled by default, Behind a flag, '
'Origin trial, Browser Intervention, and Deprecated. If '
'status. Applies to: Enabled by default, In developer trial, '
'Browser Intervention, and Deprecated. If '
'the flag is \'test\' rather than \'experimental\' set '
'status to In development.')
SHIPPED_WEBVIEW_HELP_TXT = ('First milestone to ship with this status. '
'Applies to Enabled by default, Browser '
'Intervention, and Deprecated.\n\n NOTE: for '
'statuses Behind a flag and Origin trial this '
'statuses In developer trial and Origin trial this '
'MUST be blank.')
# Note that the "required" argument in the following field definitions only
@ -1346,9 +1351,38 @@ class FeatureForm(forms.Form):
widget=forms.Textarea(attrs={'cols': 50, 'maxlength': 1480}),
help_text='Which pieces of the API surface are you looking to gain insight on? What metrics/measurement/feedback will you be using to validate designs? Double check that your experiment makes sense given that a large developer (e.g. a Google product or Facebook) likely can\'t use it in production due to the limits enforced by origin trials.\n\nIf Intent to Extend Origin Trial, highlight new/different areas for experimentation. Should not be an exact copy of goals from the first Intent to Experiment.')
experiment_timeline = forms.CharField(label='Experiment Timeline', required=False,
widget=forms.Textarea(attrs={'rows': 2, 'cols': 50, 'maxlength': 1480}),
help_text='When does the experiment start and expire?')
# TODO(jrobbins): Phase out this field.
experiment_timeline = forms.CharField(
label='Experiment Timeline', required=False,
widget=forms.Textarea(attrs={
'rows': 2, 'cols': 50, 'maxlength': 1480,
'placeholder': 'This field is deprecated',
'disabled': 'disabled'}),
help_text=('When does the experiment start and expire? '
'Deprecated: '
'Please use the following numeric fields instead.'))
# TODO(jrobbins and jmedley): Refine help text.
ot_milestone_desktop_start = forms.IntegerField(
required=False, label='OT desktop start',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('First desktop milestone that will support an origin '
'trial of this feature.'))
ot_milestone_desktop_end = forms.IntegerField(
required=False, label='OT milestone end',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('Last desktop milestone that will support an origin '
'trial of this feature.'))
ot_milestone_android_start = forms.IntegerField(
required=False, label='OT android start',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('First android milestone that will support an origin '
'trial of this feature.'))
ot_milestone_android_end = forms.IntegerField(
required=False, label='OT android end',
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text=('Last android milestone that will support an origin '
'trial of this feature.'))
experiment_risks = forms.CharField(label='Experiment Risks', required=False,
widget=forms.Textarea(attrs={'cols': 50, 'maxlength': 1480}),
@ -1415,7 +1449,8 @@ class FeatureForm(forms.Form):
impl_status_chrome = forms.ChoiceField(
required=False,
label='Status in Chromium', choices=IMPLEMENTATION_STATUS.items())
label='Implementation status', choices=IMPLEMENTATION_STATUS.items(),
help_text='Implementation status in Chromium')
#shipped_milestone = PlaceholderCharField(required=False,
# placeholder='First milestone the feature shipped with this status (either enabled by default or experimental)')
@ -1439,6 +1474,9 @@ class FeatureForm(forms.Form):
widget=forms.NumberInput(attrs={'placeholder': 'Milestone #'}),
help_text='Android WebView:<br/>' + SHIPPED_WEBVIEW_HELP_TXT)
flag_name = forms.CharField(label='Flag name', required=False,
help_text='Name of the flag that enables this feature.')
prefixed = forms.BooleanField(required=False, initial=False, label='Prefixed?')
search_tags = forms.CharField(label='Search tags', required=False,

Просмотреть файл

@ -80,8 +80,8 @@ form[name="feature_form"] {
}
}
.stage_form form[name="feature_form"] {
margin-bottom: 1em;
form[name="feature_form"] .stage_form {
margin-bottom: 2em;
}
.stage_form, .final_buttons {

Просмотреть файл

@ -33,11 +33,6 @@
</td>
</tr>
<tr>
<th>Blink components</th>
<td>{{ feature.blink_components | join:', ' }}</td>
</tr>
<tr>
<th>Category</th>
<td>{{ feature.category }}</td>
@ -52,12 +47,24 @@
<th>Feature stage</th>
<td>{{ feature.intent_stage }}</td>
</tr>
<tr>
<th>Search tags</th>
<td>{{ feature.search_tags | join:', ' }}</td>
</tr>
</table>
</td>
<td>
<table class="property-sheet">
<tr>
<th>Implementation status</th>
<td>{{ feature.impl_status_chrome }}</td>
</tr>
<tr>
<th>Blink components</th>
<td>{{ feature.blink_components | join:', ' }}</td>
</tr>
<tr>
<th>Tracking bug</th>
<td>
@ -79,17 +86,6 @@
{% endif %}
</td>
</tr>
<tr>
<th>Status in Chromium</th>
<td>{{ feature.impl_status_chrome }}</td>
</tr>
<tr>
<th>Search tags</th>
<td>{{ feature.search_tags | join:', ' }}</td>
</tr>
</table>
</div> <!-- flex-cols -->

Просмотреть файл

@ -18,8 +18,9 @@
{% endblock %}
{% block content %}
<section class="stage_form">
<form name="feature_form" method="POST" action="{{current_path}}">
<form name="feature_form" method="POST" action="{{current_path}}">
<section class="stage_form">
<table>
{{ feature_form }}
@ -43,17 +44,54 @@
{% endif %}
</td>
</tr>
<tr>
<th></th>
<td>
<input class="button" type="submit" value="Submit">
<button id="cancel-button" data-id="{{ feature_id }}"
type="reset">Cancel</button>
</td>
</tr>
</table>
</form>
</section>
</section>
{% if impl_status_name or impl_status_form %}
<h3>Implementation in Chromium</h3>
<section class="stage_form">
<table>
{% if impl_status_name %}
<tr>
<th>Implementation status:</th>
<td>
{% if already_on_this_impl_status %}
This feature already has implementation status:
<b>{{ impl_status_name }}</b>.
{% else %}
<input type="hidden" name="impl_status_offered"
value="{{impl_status_offered}}">
<input type="checkbox" name="set_impl_status"
id="set_impl_status">
<!-- TODO(jrobbins): When checked, make some milestone fields required. -->
<label for="set_impl_status">
Set implementation status to: <b>{{ impl_status_name }}</b>
<br>
<span class="helptext">
Check this box to update the implementation
status of this feature in Chromium.
</span>
</label>
{% endif %}
</td>
</tr>
{% endif %}
{{ impl_status_form }}
</table>
</section>
{% endif %}
<div style="padding-left: 220px" class="final_buttons">
<input class="button" type="submit" value="Submit">
<button id="cancel-button" data-id="{{ feature_id }}"
type="reset">Cancel</button>
</div>
</form>
{% endblock %}