Add new endpoint to trigger intent post
This commit is contained in:
Родитель
f87312fe95
Коммит
6d4614de01
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2024 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License")
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from typing import TypedDict
|
||||
|
||||
from api import converters
|
||||
from framework import basehandlers
|
||||
from framework import cloud_tasks_helpers
|
||||
from framework import permissions
|
||||
from internals import processes
|
||||
from internals import stage_helpers
|
||||
from internals.core_enums import INTENT_STAGES_BY_STAGE_TYPE
|
||||
from internals.core_models import FeatureEntry, Stage
|
||||
from internals.data_types import VerboseFeatureDict
|
||||
from internals.review_models import Gate
|
||||
from pages.intentpreview import compute_subject_prefix
|
||||
|
||||
|
||||
class IntentOptions(TypedDict):
|
||||
subject: str
|
||||
feature: VerboseFeatureDict
|
||||
stage_info: stage_helpers.StageTemplateInfo
|
||||
should_render_mstone_table: bool
|
||||
should_render_intents: bool
|
||||
sections_to_show: list[str]
|
||||
intent_stage: int|None
|
||||
default_url: str
|
||||
intent_cc_emails: list[str]
|
||||
|
||||
|
||||
|
||||
class IntentsAPI(basehandlers.APIHandler):
|
||||
|
||||
def do_post(self, **kwargs):
|
||||
"""Submit an intent email directly to blink-dev."""
|
||||
feature_id = int(kwargs['feature_id'])
|
||||
# Check that feature ID is valid.
|
||||
if not feature_id:
|
||||
self.abort(404, msg='No feature specified.')
|
||||
feature: FeatureEntry|None = FeatureEntry.get_by_id(feature_id)
|
||||
if feature is None:
|
||||
self.abort(404, msg=f'Feature {feature_id} not found')
|
||||
|
||||
body = self.get_json_param_dict()
|
||||
# Check that stage ID is valid.
|
||||
stage_id = body.get('stage_id')
|
||||
if not stage_id:
|
||||
self.abort(404, msg='No stage specified')
|
||||
stage: Stage|None = Stage.get_by_id(stage_id)
|
||||
if stage is None:
|
||||
self.abort(404, msg=f'Stage {stage_id} not found')
|
||||
|
||||
# Check that the user has feature edit permissions.
|
||||
redirect_resp = permissions.validate_feature_edit_permission(
|
||||
self, feature_id)
|
||||
if redirect_resp:
|
||||
return redirect_resp
|
||||
|
||||
intent_stage = INTENT_STAGES_BY_STAGE_TYPE[stage.stage_type]
|
||||
stage_info = stage_helpers.get_stage_info_for_templates(feature)
|
||||
default_url = (f'{self.request.scheme}://{self.request.host}'
|
||||
f'/feature/{feature_id}')
|
||||
|
||||
# Add gate to Chromestatus URL query string if it is found.
|
||||
gate: Gate|None = None
|
||||
if body.get('gate_id'):
|
||||
gate = Gate.get_by_id(body['gate_id'])
|
||||
if gate:
|
||||
default_url += f'?gate={gate.key.integer_id()}'
|
||||
|
||||
params: IntentOptions = {
|
||||
'subject': compute_subject_prefix(feature, intent_stage),
|
||||
'feature': converters.feature_entry_to_json_verbose(feature),
|
||||
'stage_info': stage_helpers.get_stage_info_for_templates(feature),
|
||||
'should_render_mstone_table': stage_info['should_render_mstone_table'],
|
||||
'should_render_intents': stage_info['should_render_intents'],
|
||||
'sections_to_show': processes.INTENT_EMAIL_SECTIONS.get(
|
||||
intent_stage, []),
|
||||
'intent_stage': intent_stage,
|
||||
'default_url': default_url,
|
||||
'intent_cc_emails': body.get('intent_cc_emails', [])
|
||||
}
|
||||
|
||||
cloud_tasks_helpers.enqueue_task('/tasks/email-intent-to-blink-dev',
|
||||
params)
|
||||
return {'message': 'Email task submitted successfully.'}
|
|
@ -1,8 +1,9 @@
|
|||
import {LitElement, css, html, nothing} from 'lit';
|
||||
import {SHARED_STYLES} from '../css/shared-css.js';
|
||||
import {showToastMessage} from './utils';
|
||||
import './chromedash-intent-template';
|
||||
import {openPostIntentDialog} from './chromedash-post-intent-dialog.js'
|
||||
import {STAGE_TYPES_SHIPPING} from './form-field-enums.js';
|
||||
import './chromedash-intent-template';
|
||||
|
||||
class ChromedashGuideIntentPreview extends LitElement {
|
||||
static get properties() {
|
||||
|
@ -149,6 +150,7 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
class="button inline"
|
||||
type="submit"
|
||||
value="Post directly to blink-dev"
|
||||
@click="${() => openPostIntentDialog()}"
|
||||
/>
|
||||
<chromedash-intent-template
|
||||
appTitle="${this.appTitle}"
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import {LitElement, css, html} from 'lit';
|
||||
import {SHARED_STYLES} from '../css/shared-css.js';
|
||||
|
||||
let dialogEl;
|
||||
|
||||
|
||||
export async function openPostIntentDialog() {
|
||||
if (!dialogEl) {
|
||||
dialogEl = document.createElement('chromedash-post-intent-dialog');
|
||||
document.body.appendChild(dialogEl);
|
||||
await dialogEl.updateComplete;
|
||||
}
|
||||
dialogEl.show();
|
||||
}
|
||||
|
||||
|
||||
class ChromedashPostIntentDialog extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...SHARED_STYLES,
|
||||
css`
|
||||
#prereqs-list li {
|
||||
margin-left: 8px;
|
||||
margin-bottom: 8px;
|
||||
list-style: circle;
|
||||
}
|
||||
#prereqs-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
#update-button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.float-right {
|
||||
float: right;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
show() {
|
||||
this.shadowRoot!.querySelector('sl-dialog')!.show();
|
||||
}
|
||||
|
||||
renderDialog() {
|
||||
return html` <sl-dialog label="Post intent to blink-dev">
|
||||
<p>
|
||||
TODO(DanielRyanSmith): add confirmation plus CC email selection.
|
||||
</p>
|
||||
</sl-dialog>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.renderDialog();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
'chromedash-post-intent-dialog',
|
||||
ChromedashPostIntentDialog
|
||||
);
|
|
@ -645,6 +645,11 @@ export class ChromeStatusClient {
|
|||
return this.doGet(`/features/${featureId}/process`);
|
||||
}
|
||||
|
||||
// Intents API
|
||||
async postIntentToBlinkDev(featureId, body) {
|
||||
return this.doPost(`features/${featureId}/postintent`, body);
|
||||
}
|
||||
|
||||
// Progress API
|
||||
async getFeatureProgress(featureId) {
|
||||
return this.doGet(`/features/${featureId}/progress`);
|
||||
|
|
|
@ -45,6 +45,7 @@ from internals.user_models import (
|
|||
|
||||
|
||||
OT_SUPPORT_EMAIL = 'origin-trials-support@google.com'
|
||||
BLINK_DEV_EMAIL = 'blink-dev@chromium.org'
|
||||
|
||||
|
||||
def _determine_milestone_string(ship_stages: list[Stage]) -> str:
|
||||
|
@ -864,6 +865,37 @@ class OTExtensionApprovedHandler(basehandlers.FlaskHandler):
|
|||
}
|
||||
|
||||
|
||||
class IntentToBlinkDevHandler(basehandlers.FlaskHandler):
|
||||
"""Submit an intent email directly to blink-dev."""
|
||||
IS_INTERNAL_HANDLER = True
|
||||
EMAIL_TEMPLATE_PATH = 'templates/blink/intent_to_implement.html'
|
||||
|
||||
def process_post_data(self, **kwargs):
|
||||
self.require_task_header()
|
||||
send_emails([self.build_email()])
|
||||
return {'message': 'OK'}
|
||||
|
||||
def build_email(self):
|
||||
json_data = self.get_json_param_dict()
|
||||
template_data = {
|
||||
'feature': json_data['feature'],
|
||||
'stage_info': json_data['stage_info'],
|
||||
'should_render_mston_table': json_data['should_render_mston_table'],
|
||||
'should_render_intents': json_data['should_render_intents'],
|
||||
'intent_stage': json_data['intent_stage'],
|
||||
'default_url': json_data['default_url'],
|
||||
}
|
||||
body = render_template(self.EMAIL_TEMPLATE_PATH, **template_data)
|
||||
|
||||
return {
|
||||
'to': BLINK_DEV_EMAIL,
|
||||
'cc': json_data['intent_cc_emails'],
|
||||
'subject': json_data['subject'],
|
||||
'reply_to': None,
|
||||
'html': body,
|
||||
}
|
||||
|
||||
|
||||
GLOBAL_OT_PROCESS_REMINDER_CC_LIST = [
|
||||
OT_SUPPORT_EMAIL,
|
||||
'origin-trials-timeline-updates@google.com'
|
||||
|
|
1
main.py
1
main.py
|
@ -304,6 +304,7 @@ internals_routes: list[Route] = [
|
|||
Route('/tasks/email-ot-extended', notifier.OTExtendedHandler),
|
||||
Route('/tasks/email-ot-extension-approved',
|
||||
notifier.OTExtensionApprovedHandler),
|
||||
Route('/tasks/email-intent-to-blink-dev', notifier.IntentToBlinkDevHandler),
|
||||
|
||||
# OT process reminder emails
|
||||
Route('/tasks/email-ot-first-branch', notifier.OTFirstBranchReminderHandler),
|
||||
|
|
|
@ -29,6 +29,37 @@ LAUNCH_PARAM = 'launch'
|
|||
VIEW_FEATURE_URL = '/feature'
|
||||
|
||||
|
||||
def compute_subject_prefix(feature, intent_stage):
|
||||
"""Return part of the subject line for an intent email."""
|
||||
|
||||
if intent_stage == core_enums.INTENT_IMPLEMENT:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Deprecate and Remove'
|
||||
else:
|
||||
return 'Intent to Prototype'
|
||||
elif intent_stage == core_enums.INTENT_EXPERIMENT:
|
||||
return 'Ready for Developer Testing'
|
||||
elif intent_stage == core_enums.INTENT_ORIGIN_TRIAL:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Request for Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Experiment'
|
||||
elif intent_stage == core_enums.INTENT_EXTEND_ORIGIN_TRIAL:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Extend Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Extend Experiment'
|
||||
elif intent_stage == core_enums.INTENT_SHIP:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_CODE_CHANGE_ID:
|
||||
return 'Web-Facing Change PSA'
|
||||
else:
|
||||
return 'Intent to Ship'
|
||||
elif intent_stage == core_enums.INTENT_REMOVED:
|
||||
return 'Intent to Extend Deprecation Trial'
|
||||
|
||||
return f'Intent stage "{core_enums.INTENT_STAGES[intent_stage]}"'
|
||||
|
||||
|
||||
class IntentEmailPreviewHandler(basehandlers.FlaskHandler):
|
||||
"""Show a preview of an intent email, as appropriate to the feature stage."""
|
||||
|
||||
|
@ -72,7 +103,7 @@ class IntentEmailPreviewHandler(basehandlers.FlaskHandler):
|
|||
|
||||
stage_info = stage_helpers.get_stage_info_for_templates(f)
|
||||
page_data = {
|
||||
'subject_prefix': self.compute_subject_prefix(f, intent_stage),
|
||||
'subject_prefix': compute_subject_prefix(f, intent_stage),
|
||||
'feature': feature_entry_to_json_verbose(f),
|
||||
'stage_info': stage_info,
|
||||
'should_render_mstone_table': stage_info['should_render_mstone_table'],
|
||||
|
@ -89,33 +120,3 @@ class IntentEmailPreviewHandler(basehandlers.FlaskHandler):
|
|||
page_data[INTENT_PARAM] = True
|
||||
|
||||
return page_data
|
||||
|
||||
def compute_subject_prefix(self, feature, intent_stage):
|
||||
"""Return part of the subject line for an intent email."""
|
||||
|
||||
if intent_stage == core_enums.INTENT_IMPLEMENT:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Deprecate and Remove'
|
||||
else:
|
||||
return 'Intent to Prototype'
|
||||
elif intent_stage == core_enums.INTENT_EXPERIMENT:
|
||||
return 'Ready for Developer Testing'
|
||||
elif intent_stage == core_enums.INTENT_ORIGIN_TRIAL:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Request for Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Experiment'
|
||||
elif intent_stage == core_enums.INTENT_EXTEND_ORIGIN_TRIAL:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_DEPRECATION_ID:
|
||||
return 'Intent to Extend Deprecation Trial'
|
||||
else:
|
||||
return 'Intent to Extend Experiment'
|
||||
elif intent_stage == core_enums.INTENT_SHIP:
|
||||
if feature.feature_type == core_enums.FEATURE_TYPE_CODE_CHANGE_ID:
|
||||
return 'Web-Facing Change PSA'
|
||||
else:
|
||||
return 'Intent to Ship'
|
||||
elif intent_stage == core_enums.INTENT_REMOVED:
|
||||
return 'Intent to Extend Deprecation Trial'
|
||||
|
||||
return 'Intent stage "%s"' % core_enums.INTENT_STAGES[intent_stage]
|
||||
|
|
|
@ -45,7 +45,35 @@
|
|||
{% if intent %}
|
||||
<section>
|
||||
<h3>Copy and send this text for your "Intent to ..." email</h3>
|
||||
<p>Email to</p>
|
||||
<div class="subject">
|
||||
blink-dev@chromium.org
|
||||
</div>
|
||||
|
||||
<p>Subject</p>
|
||||
<div class="subject">
|
||||
{{subject_prefix}}:
|
||||
{{feature.name}}
|
||||
</div>
|
||||
|
||||
{#
|
||||
Insted of vertical margins, <br> elements are used to create line breaks
|
||||
that can be copied and pasted into a text editor.
|
||||
#}
|
||||
|
||||
<p>Body
|
||||
<span class="tooltip copy-text" style="float:right"
|
||||
title="Copy text to clipboard">
|
||||
<a href="#" data-tooltip>
|
||||
<iron-icon icon="chromestatus:content_copy"
|
||||
id="copy-email-body"></iron-icon>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="email">
|
||||
{% include "blink/intent_to_implement.html" %}
|
||||
</div> <!-- end email body div -->
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,31 +1,3 @@
|
|||
<p>Email to</p>
|
||||
<div class="subject">
|
||||
blink-dev@chromium.org
|
||||
</div>
|
||||
|
||||
<p>Subject</p>
|
||||
<div class="subject">
|
||||
{{subject_prefix}}:
|
||||
{{feature.name}}
|
||||
</div>
|
||||
|
||||
{#
|
||||
Insted of vertical margins, <br> elements are used to create line breaks
|
||||
that can be copied and pasted into a text editor.
|
||||
#}
|
||||
|
||||
<p>Body
|
||||
<span class="tooltip copy-text" style="float:right"
|
||||
title="Copy text to clipboard">
|
||||
<a href="#" data-tooltip>
|
||||
<iron-icon icon="chromestatus:content_copy"
|
||||
id="copy-email-body"></iron-icon>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="email">
|
||||
|
||||
<h4>Contact emails</h4>
|
||||
{% if not feature.browsers.chrome.owners %}None{% endif %}
|
||||
{% for owner in feature.browsers.chrome.owners %}
|
||||
|
@ -336,5 +308,3 @@
|
|||
This intent message was generated by
|
||||
<a href="https://chromestatus.com">Chrome Platform Status</a>.
|
||||
</small></div>
|
||||
|
||||
</div> <!-- end email body div -->
|
||||
|
|
Загрузка…
Ссылка в новой задаче