Pull intent template from server endpoint
This commit is contained in:
Родитель
70ed6f2437
Коммит
1d03e0cece
|
@ -14,10 +14,14 @@
|
|||
|
||||
from typing import TypedDict
|
||||
|
||||
from flask import render_template
|
||||
|
||||
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.review_models import Gate
|
||||
|
@ -36,6 +40,45 @@ class IntentOptions(TypedDict):
|
|||
|
||||
class IntentsAPI(basehandlers.APIHandler):
|
||||
|
||||
def do_get(self, **kwargs):
|
||||
"""Get the body of a draft intent."""
|
||||
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')
|
||||
|
||||
# Check that stage ID is valid.
|
||||
stage_id = int(kwargs['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
|
||||
|
||||
stage_info = stage_helpers.get_stage_info_for_templates(feature)
|
||||
intent_stage = INTENT_STAGES_BY_STAGE_TYPE[stage.stage_type]
|
||||
default_url = (f'{self.request.scheme}://{self.request.host}'
|
||||
f'/feature/{feature_id}')
|
||||
template_data = {
|
||||
'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'],
|
||||
'intent_stage': intent_stage,
|
||||
'default_url': default_url,
|
||||
}
|
||||
|
||||
return render_template('blink/intent_to_implement.html', **template_data)
|
||||
|
||||
def do_post(self, **kwargs):
|
||||
"""Submit an intent email directly to blink-dev."""
|
||||
feature_id = int(kwargs['feature_id'])
|
||||
|
@ -48,7 +91,7 @@ class IntentsAPI(basehandlers.APIHandler):
|
|||
|
||||
body = self.get_json_param_dict()
|
||||
# Check that stage ID is valid.
|
||||
stage_id = body.get('stage_id')
|
||||
stage_id = int(kwargs['stage_id'])
|
||||
if not stage_id:
|
||||
self.abort(404, msg='No stage specified')
|
||||
stage: Stage|None = Stage.get_by_id(stage_id)
|
||||
|
|
|
@ -3,6 +3,9 @@ import {SHARED_STYLES} from '../css/shared-css.js';
|
|||
import {showToastMessage} from './utils';
|
||||
import {openPostIntentDialog} from './chromedash-post-intent-dialog.js';
|
||||
import {
|
||||
GATE_TYPES,
|
||||
FEATURE_TYPES,
|
||||
INTENT_STAGES,
|
||||
STAGE_TYPES_DEV_TRIAL,
|
||||
STAGE_TYPES_SHIPPING,
|
||||
} from './form-field-enums.js';
|
||||
|
@ -18,6 +21,8 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
stage: {type: Object},
|
||||
gate: {type: Object},
|
||||
loading: {type: Boolean},
|
||||
subject: {type:String},
|
||||
intentBody: {type: String},
|
||||
displayFeatureUnlistedWarning: {type: Boolean},
|
||||
};
|
||||
}
|
||||
|
@ -31,6 +36,8 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
this.stage = undefined;
|
||||
this.gate = undefined;
|
||||
this.loading = true;
|
||||
this.subject = '';
|
||||
this.intentBody = '';
|
||||
this.displayFeatureUnlistedWarning = false;
|
||||
}
|
||||
|
||||
|
@ -79,6 +86,7 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
])
|
||||
.then(([feature, gates]) => {
|
||||
this.feature = feature;
|
||||
document.title = `${this.feature.name} - ${this.appTitle}`;
|
||||
// TODO(DanielRyanSmith): only fetch a single gate based on given ID.
|
||||
if (this.gateId) {
|
||||
this.gate = gates.gates.find(gate => gate.id === this.gateId);
|
||||
|
@ -94,12 +102,15 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.feature.name) {
|
||||
document.title = `${this.feature.name} - ${this.appTitle}`;
|
||||
}
|
||||
if (this.feature.unlisted) {
|
||||
this.displayFeatureUnlistedWarning = true;
|
||||
}
|
||||
this.subject = `${this.computeSubjectPrefix()}: ${this.feature.name}`;
|
||||
// Finally, get the contents of the intent based on the feature/stage.
|
||||
return window.csClient.getIntentBody(this.featureId, this.stage.id);
|
||||
})
|
||||
.then(intentBody => {
|
||||
this.intentBody = intentBody;
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -132,9 +143,79 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
</div>`;
|
||||
}
|
||||
|
||||
computeSubjectPrefix() {
|
||||
// DevTrials don't have a gate associated with their stage.
|
||||
if (!this.gate) {
|
||||
return 'Ready for Developer Testing';
|
||||
}
|
||||
if (
|
||||
this.gate.gate_type === GATE_TYPES.API_PROTOTYPE ||
|
||||
this.gate.gate_type === GATE_TYPES.API_PLAN
|
||||
) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Intent to Deprecate and Remove';
|
||||
}
|
||||
return 'Intent to Prototype';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_ORIGIN_TRIAL) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Request for Deprecation Trial';
|
||||
}
|
||||
return 'Intent to Experiment';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_EXTEND_ORIGIN_TRIAL) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Intent to Extend Deprecation Trial';
|
||||
}
|
||||
return 'Intent to Extend Experiment';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_SHIP) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_CODE_CHANGE_ID[0]
|
||||
) {
|
||||
return 'Web-Facing Change PSA';
|
||||
}
|
||||
return 'Intent to Ship';
|
||||
}
|
||||
return `Intent stage "${INTENT_STAGES[this.feature.intent_stage]}"`;
|
||||
}
|
||||
|
||||
renderSkeletonSection() {
|
||||
return html`
|
||||
<section>
|
||||
<h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
|
||||
<p>
|
||||
<sl-skeleton effect="sheen"></sl-skeleton>
|
||||
<sl-skeleton effect="sheen"></sl-skeleton>
|
||||
</p>
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
renderSkeletons() {
|
||||
return html`
|
||||
<div id="feature" style="margin-top: 65px;">
|
||||
${this.renderSkeletonSection()} ${this.renderSkeletonSection()}
|
||||
${this.renderSkeletonSection()} ${this.renderSkeletonSection()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.feature) return nothing;
|
||||
|
||||
if (this.loading) {
|
||||
return this.renderSkeletons();
|
||||
}
|
||||
return html`
|
||||
<div id="content">
|
||||
<div id="subheader">
|
||||
|
@ -171,7 +252,8 @@ class ChromedashGuideIntentPreview extends LitElement {
|
|||
appTitle="${this.appTitle}"
|
||||
.feature=${this.feature}
|
||||
.stage=${this.stage}
|
||||
.gate=${this.gate}
|
||||
subject="${this.subject}"
|
||||
intentBody="${this.intentBody}"
|
||||
>
|
||||
</chromedash-intent-template>
|
||||
</section>
|
||||
|
|
|
@ -1,37 +1,22 @@
|
|||
import {LitElement, css, html, nothing} from 'lit';
|
||||
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
|
||||
import {SHARED_STYLES} from '../css/shared-css.js';
|
||||
import {
|
||||
GATE_TYPES,
|
||||
FEATURE_TYPES,
|
||||
INTENT_STAGES,
|
||||
STAGE_DEP_PLAN,
|
||||
STAGE_TYPES_INTENT_EXPERIMENT,
|
||||
STAGE_TYPES_ORIGIN_TRIAL,
|
||||
STAGE_TYPES_PROTOTYPE,
|
||||
STAGE_TYPES_SHIPPING,
|
||||
OT_EXTENSION_STAGE_TYPES,
|
||||
STAGE_BLINK_EVAL_READINESS,
|
||||
STAGE_TYPES_DEV_TRIAL,
|
||||
} from './form-field-enums.js';
|
||||
import {showToastMessage} from './utils.js';
|
||||
|
||||
export class ChromedashIntentTemplate extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
appTitle: {type: String},
|
||||
feature: {type: Object},
|
||||
stage: {type: Object},
|
||||
gate: {type: Object},
|
||||
displayFeatureUnlistedWarning: {type: Boolean},
|
||||
subject: {type: String},
|
||||
intentBody: {type: String},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.appTitle = '';
|
||||
this.feature = {};
|
||||
this.stage = undefined;
|
||||
this.gate = undefined;
|
||||
this.subject = '';
|
||||
this.intentBody = '';
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
|
@ -180,904 +165,12 @@ export class ChromedashIntentTemplate extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
computeSubjectPrefix() {
|
||||
// DevTrials don't have a gate associated with their stage.
|
||||
if (!this.gate) {
|
||||
return 'Ready for Developer Testing';
|
||||
}
|
||||
if (
|
||||
this.gate.gate_type === GATE_TYPES.API_PROTOTYPE ||
|
||||
this.gate.gate_type === GATE_TYPES.API_PLAN
|
||||
) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Intent to Deprecate and Remove';
|
||||
}
|
||||
return 'Intent to Prototype';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_ORIGIN_TRIAL) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Request for Deprecation Trial';
|
||||
}
|
||||
return 'Intent to Experiment';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_EXTEND_ORIGIN_TRIAL) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_DEPRECATION_ID[0]
|
||||
) {
|
||||
return 'Intent to Extend Deprecation Trial';
|
||||
}
|
||||
return 'Intent to Extend Experiment';
|
||||
}
|
||||
if (this.gate.gate_type === GATE_TYPES.API_SHIP) {
|
||||
if (
|
||||
this.feature.feature_type_int ===
|
||||
FEATURE_TYPES.FEATURE_TYPE_CODE_CHANGE_ID[0]
|
||||
) {
|
||||
return 'Web-Facing Change PSA';
|
||||
}
|
||||
return 'Intent to Ship';
|
||||
}
|
||||
return `Intent stage "${INTENT_STAGES[this.feature.intent_stage]}"`;
|
||||
}
|
||||
|
||||
renderOwners() {
|
||||
const owners = this.feature.owner_emails;
|
||||
let ownersHTML = html`None`;
|
||||
if (owners) {
|
||||
ownersHTML = owners.map((o, i) => {
|
||||
if (i + 1 === owners.length) {
|
||||
return html`<a href="mailto:${o}">${o}</a>`;
|
||||
}
|
||||
return html`<a href="mailto:${o}">${o}</a>, `;
|
||||
});
|
||||
}
|
||||
return html`<h4>Contact emails</h4>
|
||||
${ownersHTML}`;
|
||||
}
|
||||
|
||||
renderExplainerLinks() {
|
||||
const explainerLinks = this.feature.explainer_links;
|
||||
if (this.feature.feature_type_int === 2) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
let explainerLinksHTML = html`None`;
|
||||
if (explainerLinks && explainerLinks.length > 0) {
|
||||
explainerLinksHTML = explainerLinks.map((link, i) => {
|
||||
if (i === 0) {
|
||||
return html`<a href="${link}">${link}</a>`;
|
||||
}
|
||||
return html`<br /><a href="${link}">${link}</a>`;
|
||||
});
|
||||
}
|
||||
return html`<br /><br />
|
||||
<h4>Explainer</h4>
|
||||
${explainerLinksHTML}`;
|
||||
}
|
||||
|
||||
renderSpecification() {
|
||||
const spec = this.feature.standards?.spec;
|
||||
let specHTML = html`None`;
|
||||
if (spec) {
|
||||
specHTML = html`<a href="${spec}">${spec}</a>`;
|
||||
}
|
||||
return html`<br /><br />
|
||||
<h4>Specification</h4>
|
||||
${specHTML}`;
|
||||
}
|
||||
|
||||
renderDesignDocs() {
|
||||
const docs = this.feature.resources?.docs;
|
||||
if (!docs || docs.length === 0) return nothing;
|
||||
|
||||
let docsHTML = html`None`;
|
||||
docsHTML = docs.map((link, i) => {
|
||||
if (i === 0) {
|
||||
return html`<a href="${link}">${link}</a>`;
|
||||
}
|
||||
return html`<br /><a href="${link}">${link}</a>`;
|
||||
});
|
||||
return html` <br /><br />
|
||||
<h4>Design docs</h4>
|
||||
${docsHTML}`;
|
||||
}
|
||||
|
||||
renderSummary() {
|
||||
const summary = this.feature.summary;
|
||||
let summaryHTML = html`None`;
|
||||
if (summary) {
|
||||
summaryHTML = html`<p>${this.feature.summary}</p>`;
|
||||
}
|
||||
return html` <br /><br />
|
||||
<h4>Summary</h4>
|
||||
${summaryHTML}`;
|
||||
}
|
||||
|
||||
renderBlinkComponents() {
|
||||
const blinkComponents = this.feature.blink_components;
|
||||
let blinkComponentsHTML = html`None`;
|
||||
if (blinkComponents && blinkComponents.length > 0) {
|
||||
blinkComponentsHTML = blinkComponents.map(
|
||||
bc =>
|
||||
html`<a
|
||||
href="https://bugs.chromium.org/p/chromium/issues/list?q=component:${bc}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>${bc}</a
|
||||
>`
|
||||
);
|
||||
}
|
||||
return html`
|
||||
<br /><br />
|
||||
<h4>Blink component</h4>
|
||||
${blinkComponentsHTML}
|
||||
`;
|
||||
}
|
||||
|
||||
renderMotivation() {
|
||||
if (
|
||||
!STAGE_TYPES_PROTOTYPE.has(this.stage?.stage_type) &&
|
||||
this.stage?.stage_type !== STAGE_DEP_PLAN
|
||||
)
|
||||
return nothing;
|
||||
const motivation = this.feature.motivation || 'None';
|
||||
|
||||
return html`
|
||||
<br /><br />
|
||||
<h4>Motivation</h4>
|
||||
<p>${motivation}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
renderInitialPublicProposal() {
|
||||
if (
|
||||
!STAGE_TYPES_PROTOTYPE.has(this.stage?.stage_type) &&
|
||||
this.stage?.stage_type !== STAGE_DEP_PLAN
|
||||
)
|
||||
return nothing;
|
||||
const initialPublicProposalUrl =
|
||||
this.feature.initial_public_proposal_url || 'None';
|
||||
|
||||
return html`
|
||||
<br /><br />
|
||||
<h4>Initial public proposal</h4>
|
||||
<a href="${initialPublicProposalUrl}">${initialPublicProposalUrl}</a>
|
||||
`;
|
||||
}
|
||||
|
||||
renderTags() {
|
||||
const tags = this.feature.tags;
|
||||
if (!tags || tags.length === 0) return nothing;
|
||||
|
||||
const tagsHTML = tags.map((t, i) => {
|
||||
if (i + 1 === tags.length) {
|
||||
return html`<a href="/features#tags:${t}">${t}</a>`;
|
||||
}
|
||||
return html`<a href="/features#tags:${t}">${t}</a>, `;
|
||||
});
|
||||
return html`
|
||||
<br /><br />
|
||||
<h4>Search tags</h4>
|
||||
${tagsHTML}
|
||||
`;
|
||||
}
|
||||
|
||||
renderTagReview() {
|
||||
const parts = [
|
||||
html` <br /><br />
|
||||
<h4>TAG review</h4>
|
||||
${this.feature.tag_review || 'None'}`,
|
||||
];
|
||||
const tagReviewStatus = this.feature.tag_review_status;
|
||||
if (tagReviewStatus) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>TAG review status</h4>
|
||||
${this.feature.tag_review_status}`
|
||||
);
|
||||
}
|
||||
return html`${parts}`;
|
||||
}
|
||||
|
||||
renderOTInfo(otStages) {
|
||||
if (otStages.length === 0) return nothing;
|
||||
const parts = [];
|
||||
otStages.forEach((s, i) => {
|
||||
const stageParts = [];
|
||||
if (s.ot_chromium_trial_name) {
|
||||
stageParts.push(
|
||||
html` <br /><br />
|
||||
<h4>Chromium Trial Name</h4>
|
||||
${s.ot_chromium_trial_name}`
|
||||
);
|
||||
}
|
||||
if (s.origin_trial_feedback_url) {
|
||||
stageParts.push(
|
||||
html` <br /><br />
|
||||
<h4>Link to origin trial feedback summary</h4>
|
||||
${s.origin_trial_feedback_url}`
|
||||
);
|
||||
}
|
||||
if (s.ot_is_deprecation_trial && s.ot_webfeature_use_counter) {
|
||||
stageParts.push(
|
||||
html` <br /><br />
|
||||
<h4>WebFeature UseCounter name</h4>
|
||||
${s.ot_webfeature_use_counter}`
|
||||
);
|
||||
}
|
||||
if (stageParts.length > 0 && s.ot_display_name) {
|
||||
stageParts.shift(
|
||||
html` <br /><br />
|
||||
<h4>
|
||||
<strong>Origin Trial</strong> ${i + 1}: ${s.ot_display_name}
|
||||
</h4>`
|
||||
);
|
||||
}
|
||||
parts.push(...stageParts);
|
||||
});
|
||||
return html`${parts}`;
|
||||
}
|
||||
|
||||
renderRisks() {
|
||||
const parts = [];
|
||||
|
||||
// Interop risks
|
||||
const interopRisks = this.feature.interop_compat_risks;
|
||||
let interopRisksHTML = html`None`;
|
||||
if (interopRisks) {
|
||||
interopRisksHTML = html`<p>${interopRisks}</p>`;
|
||||
}
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Interoperability and Compatibility</h4>
|
||||
${interopRisksHTML}`
|
||||
);
|
||||
|
||||
// Gecko risks
|
||||
parts.push(
|
||||
html` <br /><br /><i>Gecko:</i> ${this.feature.browsers.ff.view.text ||
|
||||
html`None`}`
|
||||
);
|
||||
if (this.feature.browsers.ff.view.url) {
|
||||
parts.push(html`
|
||||
(<a href="${this.feature.browsers.ff.view.url}"
|
||||
>${this.feature.browsers.ff.view.url}</a
|
||||
>)
|
||||
`);
|
||||
}
|
||||
const geckoNotes = this.feature.browsers.ff.view.notes;
|
||||
if (geckoNotes) {
|
||||
parts.push(geckoNotes);
|
||||
}
|
||||
|
||||
// WebKit risks
|
||||
parts.push(
|
||||
html` <br /><br /><i>WebKit:</i> ${this.feature.browsers.safari.view
|
||||
.text || html`None`}`
|
||||
);
|
||||
if (this.feature.browsers.safari.view.url) {
|
||||
parts.push(html`
|
||||
(<a href="${this.feature.browsers.safari.view.url}"
|
||||
>${this.feature.browsers.safari.view.url}</a
|
||||
>)
|
||||
`);
|
||||
}
|
||||
const webKitNotes = this.feature.browsers.safari.view.notes;
|
||||
if (webKitNotes) {
|
||||
parts.push(webKitNotes);
|
||||
}
|
||||
|
||||
// Web developer risks
|
||||
parts.push(
|
||||
html` <br /><br /><i>Web developers</i>:
|
||||
${this.feature.browsers.webdev.view.text || html`None`}`
|
||||
);
|
||||
if (this.feature.browsers.webdev.view.url) {
|
||||
parts.push(html`
|
||||
(<a href="${this.feature.browsers.webdev.view.url}"
|
||||
>${this.feature.browsers.webdev.view.url}</a
|
||||
>)
|
||||
`);
|
||||
}
|
||||
const webdevNotes = this.feature.browsers.webdev.view.notes;
|
||||
if (webdevNotes) {
|
||||
parts.push(webdevNotes);
|
||||
}
|
||||
|
||||
parts.push(html` <br /><br /><i>Other signals</i>: `);
|
||||
if (this.feature.browsers.other.view.notes) {
|
||||
parts.push(html`${this.feature.browsers.other.view.notes}`);
|
||||
}
|
||||
if (this.feature.ergonomics_risks) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Ergonomics</h4>
|
||||
<p>${this.feature.ergonomics_risks}</p>`
|
||||
);
|
||||
}
|
||||
if (this.feature.activation_risks) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Activation</h4>
|
||||
<p>${this.feature.activation_risks}</p>`
|
||||
);
|
||||
}
|
||||
if (this.feature.security_risks) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Security</h4>
|
||||
<p>${this.feature.security_risks}</p>`
|
||||
);
|
||||
}
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>WebView application risks</h4>
|
||||
<p style="font-style: italic">
|
||||
Does this intent deprecate or change behavior of existing APIs, such
|
||||
that it has potentially high risk for Android WebView-based
|
||||
applications?
|
||||
</p>
|
||||
<p>${this.feature.webview_risks || html`None`}</p>`
|
||||
);
|
||||
|
||||
return html` <br /><br />
|
||||
<h4>Risks</h4>
|
||||
<div style="margin-left: 4em;">${parts}</div>`;
|
||||
}
|
||||
|
||||
renderExperimentGoals() {
|
||||
// Only show this section for experiment intents.
|
||||
if (
|
||||
this.stage &&
|
||||
!STAGE_TYPES_INTENT_EXPERIMENT.has(this.stage?.stage_type)
|
||||
)
|
||||
return nothing;
|
||||
const parts = [
|
||||
html` <br /><br />
|
||||
<h4>Goals for experimentation</h4>
|
||||
<p>${this.feature.experiment_goals || 'None'}</p>`,
|
||||
];
|
||||
if (this.feature.experiment_timeline) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Experiment timeline</h4>
|
||||
<p>${this.feature.experiment_timeline}</p>`
|
||||
);
|
||||
}
|
||||
if (
|
||||
OT_EXTENSION_STAGE_TYPES.has(this.stage?.stage_type) &&
|
||||
this.stage.experiment_extension_reason
|
||||
) {
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Reason this experiment is being extended</h4>
|
||||
<p>${stage.experiment_extension_reason}</p>`
|
||||
);
|
||||
}
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Ongoing technical constraints</h4>
|
||||
${this.feature.ongoing_constraints || 'None'}`
|
||||
);
|
||||
return html`${parts}`;
|
||||
}
|
||||
|
||||
renderDebuggability() {
|
||||
return html` <br /><br />
|
||||
<h4>Debuggability</h4>
|
||||
<p>${this.feature.debuggability || 'None'}</p>`;
|
||||
}
|
||||
|
||||
renderAllPlatforms() {
|
||||
// This section is only shown for experimental and shipping intents.
|
||||
if (
|
||||
this.stage &&
|
||||
!STAGE_TYPES_INTENT_EXPERIMENT.has(this.stage?.stage_type) &&
|
||||
!STAGE_TYPES_SHIPPING.has(this.stage?.stage_type)
|
||||
)
|
||||
return nothing;
|
||||
let descriptionHTML = nothing;
|
||||
if (this.feature.all_platforms_descr) {
|
||||
descriptionHTML = html`<p>${this.feature.all_platforms_descr}</p>`;
|
||||
}
|
||||
return html` <br /><br />
|
||||
<h4>
|
||||
Will this feature be supported on all six Blink platforms (Windows, Mac,
|
||||
Linux, ChromeOS, Android, and Android WebView)?
|
||||
</h4>
|
||||
${this.feature.all_platforms ? 'Yes' : 'No'}<br />
|
||||
${descriptionHTML}`;
|
||||
}
|
||||
|
||||
renderWPT() {
|
||||
let descriptionHTML = nothing;
|
||||
if (this.feature.wpt_descr) {
|
||||
descriptionHTML = html`<br />
|
||||
<p>${this.feature.wpt_descr}</p>`;
|
||||
}
|
||||
return html` <br /><br />
|
||||
<h4>
|
||||
Is this feature fully tested by
|
||||
<a
|
||||
href="https://chromium.googlesource.com/chromium/src/+/main/docs/testing/web_platform_tests.md"
|
||||
>web-platform-tests</a
|
||||
>?
|
||||
</h4>
|
||||
${this.feature.wpt ? 'Yes' : 'No'} ${descriptionHTML}`;
|
||||
}
|
||||
|
||||
renderDevTrialInstructions() {
|
||||
if (!this.feature.devtrial_instructions) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>DevTrial instructions</h4>
|
||||
<a href="${this.feature.devtrial_instructions}"
|
||||
>${this.feature.devtrial_instructions}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
renderFlagName() {
|
||||
return html` <br /><br />
|
||||
<h4>Flag name on chrome://flags</h4>
|
||||
${this.feature.flag_name || 'None'}`;
|
||||
}
|
||||
|
||||
renderFinchInfo() {
|
||||
const parts = [
|
||||
html` <br /><br />
|
||||
<h4>Finch feature name</h4>
|
||||
${this.feature.finch_name || 'None'}`,
|
||||
];
|
||||
let nonFinchJustificationHTML = html`None`;
|
||||
if (this.feature.non_finch_justification) {
|
||||
nonFinchJustificationHTML = html` <p>
|
||||
${this.feature.non_finch_justification}
|
||||
</p>`;
|
||||
}
|
||||
parts.push(
|
||||
html` <br /><br />
|
||||
<h4>Non-finch justification</h4>
|
||||
${nonFinchJustificationHTML}`
|
||||
);
|
||||
return html`${parts}`;
|
||||
}
|
||||
|
||||
renderEmbedderSupport() {
|
||||
return html` <br /><br />
|
||||
<h4>Requires code in //chrome?</h4>
|
||||
${this.feature.requires_embedder_support ? 'Yes' : 'No'}`;
|
||||
}
|
||||
|
||||
renderTrackingBug() {
|
||||
if (!this.feature.browsers.chrome.bug) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Tracking bug</h4>
|
||||
<a href="${this.feature.browsers.chrome.bug}"
|
||||
>${this.feature.browsers.chrome.bug}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
renderLaunchBug() {
|
||||
if (!this.feature.launch_bug_url) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Launch bug</h4>
|
||||
<a href="${this.feature.launch_bug_url}"
|
||||
>${this.feature.launch_bug_url}</a
|
||||
>`;
|
||||
}
|
||||
|
||||
renderMeasurement() {
|
||||
if (!this.feature.measurement) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Measurement</h4>
|
||||
${this.feature.measurement}`;
|
||||
}
|
||||
|
||||
renderAvailabilityExpectation() {
|
||||
if (!this.feature.availability_expectation) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Availability expectation</h4>
|
||||
${this.feature.availability_expectation}`;
|
||||
}
|
||||
|
||||
renderAdoptionExpectation() {
|
||||
if (!this.feature.adoption_expectation) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Adoption expectation</h4>
|
||||
${this.feature.adoption_expectation}`;
|
||||
}
|
||||
|
||||
renderAdoptionPlan() {
|
||||
if (!this.feature.adoption_plan) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Adoption plan</h4>
|
||||
${this.feature.adoption_plan}`;
|
||||
}
|
||||
|
||||
renderNonOSSDeps() {
|
||||
if (!this.feature.non_oss_deps) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Non-OSS dependencies</h4>
|
||||
<p style="font-style: italic">
|
||||
Does the feature depend on any code or APIs outside the Chromium open
|
||||
source repository and its open-source dependencies to function?
|
||||
</p>
|
||||
|
||||
${this.feature.non_oss_deps}`;
|
||||
}
|
||||
|
||||
renderSampleLinks() {
|
||||
const samples = this.feature.resources?.samples || [];
|
||||
// Only show for shipping stages.
|
||||
if (
|
||||
(!STAGE_TYPES_SHIPPING.has(this.stage?.stage_type) &&
|
||||
!this.stage?.stage_type !== STAGE_BLINK_EVAL_READINESS) ||
|
||||
samples.length === 0
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <br /><br />
|
||||
<h4>Sample links</h4>
|
||||
${samples.map(url => html`<br /><a href="${url}">${url}</a>`)}`;
|
||||
}
|
||||
|
||||
renderDesktopMilestonesTable(dtStages, otStages, shipStages) {
|
||||
const shipStagesHTML = shipStages.map(s => {
|
||||
if (!s.desktop_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>Shipping on desktop</td>
|
||||
<td>${s.desktop_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const otStagesHTML = otStages.map((s, i) => {
|
||||
const parts = [];
|
||||
const identifier = otStages.length > 1 ? `${i + 1} ` : '';
|
||||
if (s.desktop_first) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}desktop first</td>
|
||||
<td>${s.desktop_first}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
if (s.desktop_last) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}desktop last</td>
|
||||
<td>${s.desktop_last}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
s.extensions.forEach((es, j) => {
|
||||
const extensionIdentifier = s.extensions.length > 1 ? `${j + 1} ` : '';
|
||||
if (es.desktop_last) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>
|
||||
Origin trial ${identifier}extension ${extensionIdentifier}end
|
||||
milestone
|
||||
</td>
|
||||
<td>${es.desktop_last}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
});
|
||||
return html`${parts}`;
|
||||
});
|
||||
|
||||
const dtStagesHTML = dtStages.map(s => {
|
||||
if (!s.desktop_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>DevTrial on desktop</td>
|
||||
<td>${s.desktop_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
return html` <table>
|
||||
${shipStagesHTML}${otStagesHTML}${dtStagesHTML}
|
||||
</table>`;
|
||||
}
|
||||
|
||||
renderAndroidMilestonesTable(dtStages, otStages, shipStages) {
|
||||
const shipStagesHTML = shipStages.map(s => {
|
||||
if (!s.android_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>Shipping on Android</td>
|
||||
<td>${s.android_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const otStagesHTML = otStages.map((s, i) => {
|
||||
const parts = [];
|
||||
const identifier = otStages.length > 1 ? `${i + 1} ` : '';
|
||||
if (s.android_first) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}Android first</td>
|
||||
<td>${s.android_first}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
if (s.android_last) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}Android last</td>
|
||||
<td>${s.android_last}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
return html`${parts}`;
|
||||
});
|
||||
|
||||
const dtStagesHTML = dtStages.map(s => {
|
||||
if (!s.android_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>DevTrial on Android</td>
|
||||
<td>${s.android_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
return html` <table>
|
||||
${shipStagesHTML}${otStagesHTML}${dtStagesHTML}
|
||||
</table>`;
|
||||
}
|
||||
|
||||
renderWebViewMilestonesTable(otStages, shipStages) {
|
||||
const shipStagesHTML = shipStages.map(s => {
|
||||
if (!s.webview_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>Shipping on WebView</td>
|
||||
<td>${s.webview_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const otStagesHTML = otStages.map((s, i) => {
|
||||
const parts = [];
|
||||
const identifier = otStages.length > 1 ? `${i + 1} ` : '';
|
||||
if (s.webview_first) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}WebView first</td>
|
||||
<td>${s.webview_first}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
if (s.webview_last) {
|
||||
parts.push(
|
||||
html` <tr>
|
||||
<td>Origin trial ${identifier}WebView last</td>
|
||||
<td>${s.webview_last}</td>
|
||||
</tr>`
|
||||
);
|
||||
}
|
||||
return html`${parts}`;
|
||||
});
|
||||
return html` <table>
|
||||
${shipStagesHTML}${otStagesHTML}
|
||||
</table>`;
|
||||
}
|
||||
|
||||
renderIOSMilestonesTable(dtStages, shipStages) {
|
||||
const shipStagesHTML = shipStages.map(s => {
|
||||
if (!s.ios_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>Shipping on iOS</td>
|
||||
<td>${s.ios_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
const dtStagesHTML = dtStages.map(s => {
|
||||
if (!s.ios_first) {
|
||||
return nothing;
|
||||
}
|
||||
return html` <tr>
|
||||
<td>DevTrial on iOS</td>
|
||||
<td>${s.ios_first}</td>
|
||||
</tr>`;
|
||||
});
|
||||
return html` <table>
|
||||
${shipStagesHTML}${dtStagesHTML}
|
||||
</table>`;
|
||||
}
|
||||
|
||||
renderEstimatedMilestones(dtStages, otStages, shipStages) {
|
||||
// Don't display the table if no milestones are defined.
|
||||
if (
|
||||
!(
|
||||
shipStages.some(
|
||||
s =>
|
||||
s.desktop_first || s.android_first || s.ios_first || s.webview_first
|
||||
) ||
|
||||
otStages.some(
|
||||
s =>
|
||||
s.desktop_first ||
|
||||
s.android_first ||
|
||||
s.webview_first ||
|
||||
s.desktop_last ||
|
||||
s.android_last ||
|
||||
s.webview_last
|
||||
) ||
|
||||
dtStages.some(s => s.desktop_first || s.android_first || s.ios_first)
|
||||
)
|
||||
) {
|
||||
return html` <br /><br />
|
||||
<h4>Estimated milestones</h4>
|
||||
<p>No milestones specified</p>`;
|
||||
}
|
||||
return html` <br /><br />
|
||||
<h4>Estimated milestones</h4>
|
||||
${this.renderDesktopMilestonesTable(dtStages, otStages, shipStages)}
|
||||
${this.renderAndroidMilestonesTable(dtStages, otStages, shipStages)}
|
||||
${this.renderWebViewMilestonesTable(otStages, shipStages)}
|
||||
${this.renderIOSMilestonesTable(dtStages, shipStages)}`;
|
||||
}
|
||||
|
||||
renderAnticipatedSpecChanges() {
|
||||
// Only show for shipping stages or if the anticipated spec changes
|
||||
// field is filled.
|
||||
if (
|
||||
!STAGE_TYPES_SHIPPING.has(this.stage?.stage_type) &&
|
||||
!this.feature.anticipated_spec_changes
|
||||
)
|
||||
return nothing;
|
||||
const anticipatedSpecChanges =
|
||||
this.feature.anticipated_spec_changes || 'None';
|
||||
return html` <br /><br />
|
||||
<h4>Anticipated spec changes</h4>
|
||||
<p style="font-style: italic">
|
||||
Open questions about a feature may be a source of future web compat or
|
||||
interop issues. Please list open issues (e.g. links to known github
|
||||
issues in the project for the feature specification) whose resolution
|
||||
may introduce web compat/interop risk (e.g., changing to naming or
|
||||
structure of the API in a non-backward-compatible way).
|
||||
</p>
|
||||
|
||||
${anticipatedSpecChanges}`;
|
||||
}
|
||||
|
||||
renderChromestatusLink() {
|
||||
let urlSuffix;
|
||||
if (this.gate) {
|
||||
urlSuffix = `feature/${this.feature.id}?gate=${this.gate.id}`;
|
||||
} else {
|
||||
urlSuffix = `feature/${this.feature.id}`;
|
||||
}
|
||||
const url = `${window.location.protocol}//${window.location.host}/${urlSuffix}`;
|
||||
return html` <br /><br />
|
||||
<h4>Link to entry on ${this.appTitle}</h4>
|
||||
<a href="${url}">${url}</a>`;
|
||||
}
|
||||
|
||||
renderIntents(protoStages, dtStages, otStages, shipStages) {
|
||||
const parts = [];
|
||||
protoStages.forEach(s => {
|
||||
if (s.intent_thread_url) {
|
||||
parts.push(
|
||||
html` Intent to Prototype:
|
||||
<a href="${s.intent_thread_url}">${s.intent_thread_url}</a> <br />`
|
||||
);
|
||||
}
|
||||
});
|
||||
dtStages.forEach(s => {
|
||||
if (s.announcement_url) {
|
||||
parts.push(
|
||||
html`Ready for Trial:
|
||||
<a href="${s.announcement_url}">${s.announcement_url}</a> <br />`
|
||||
);
|
||||
}
|
||||
});
|
||||
otStages.forEach((s, i) => {
|
||||
const identifier = otStages.length > 1 ? ` ${i + 1}` : '';
|
||||
if (s.intent_thread_url) {
|
||||
parts.push(
|
||||
html`Intent to Experiment${identifier}:
|
||||
<a href="${s.intent_thread_url}">${s.intent_thread_url}</a> <br />`
|
||||
);
|
||||
}
|
||||
s.extensions?.forEach((es, j) => {
|
||||
const extensionIdentifier =
|
||||
s.extensions.length > 1 ? ` (Extension ${j + 1})` : '';
|
||||
if (es.intent_thread_url) {
|
||||
parts.push(
|
||||
html` Intent to Extend Experiment${extensionIdentifier}:
|
||||
<a href="${es.intent_thread_url}">${es.intent_thread_url}</a>
|
||||
<br />`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
shipStages.forEach(s => {
|
||||
if (s.intent_thread_url) {
|
||||
parts.push(
|
||||
html` Intent to Ship:
|
||||
<a href="${s.intent_thread_url}">${s.intent_thread_url}</a> <br />`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (parts.length === 0) return nothing;
|
||||
return html` <br /><br />
|
||||
<h4>Links to previous Intent discussions</h4>
|
||||
|
||||
${parts}`;
|
||||
}
|
||||
|
||||
renderFooterNote() {
|
||||
const url = `${window.location.protocol}//${window.location.host}/`;
|
||||
return html` <br /><br />
|
||||
<div>
|
||||
<small
|
||||
>This intent message was generated by
|
||||
<a href="${url}">${this.appTitle}</a>.</small
|
||||
>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderEmailBody() {
|
||||
const protoStages = this.feature.stages.filter(s =>
|
||||
STAGE_TYPES_PROTOTYPE.has(s.stage_type)
|
||||
);
|
||||
const dtStages = this.feature.stages.filter(s =>
|
||||
STAGE_TYPES_DEV_TRIAL.has(s.stage_type)
|
||||
);
|
||||
const otStages = this.feature.stages.filter(s =>
|
||||
STAGE_TYPES_ORIGIN_TRIAL.has(s.stage_type)
|
||||
);
|
||||
const shipStages = this.feature.stages.filter(s =>
|
||||
STAGE_TYPES_SHIPPING.has(s.stage_type)
|
||||
);
|
||||
return html`${[
|
||||
this.renderOwners(),
|
||||
this.renderExplainerLinks(),
|
||||
this.renderSpecification(),
|
||||
this.renderDesignDocs(),
|
||||
this.renderSummary(),
|
||||
this.renderBlinkComponents(),
|
||||
this.renderMotivation(),
|
||||
this.renderInitialPublicProposal(),
|
||||
this.renderTags(),
|
||||
this.renderTagReview(),
|
||||
this.renderOTInfo(otStages),
|
||||
this.renderRisks(),
|
||||
this.renderExperimentGoals(),
|
||||
this.renderDebuggability(),
|
||||
this.renderAllPlatforms(),
|
||||
this.renderWPT(),
|
||||
this.renderDevTrialInstructions(),
|
||||
this.renderFlagName(),
|
||||
this.renderFinchInfo(),
|
||||
this.renderEmbedderSupport(),
|
||||
this.renderTrackingBug(),
|
||||
this.renderLaunchBug(),
|
||||
this.renderMeasurement(),
|
||||
this.renderAvailabilityExpectation(),
|
||||
this.renderAdoptionExpectation(),
|
||||
this.renderAdoptionPlan(),
|
||||
this.renderNonOSSDeps(),
|
||||
this.renderSampleLinks(),
|
||||
this.renderEstimatedMilestones(dtStages, otStages, shipStages),
|
||||
this.renderAnticipatedSpecChanges(),
|
||||
this.renderChromestatusLink(),
|
||||
this.renderIntents(protoStages, dtStages, otStages, shipStages),
|
||||
this.renderFooterNote(),
|
||||
]}`;
|
||||
if (this.intentBody) {
|
||||
// Needed for rendering HTML format returned from the API.
|
||||
return unsafeHTML(this.intentBody);
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -1090,7 +183,7 @@ export class ChromedashIntentTemplate extends LitElement {
|
|||
<p>Subject</p>
|
||||
<div class="email-content-border">
|
||||
<div class="subject email-content-div" id="email-subject-content">
|
||||
${this.computeSubjectPrefix()}: ${this.feature.name}
|
||||
${this.subject}
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
|
|
|
@ -3,116 +3,13 @@ import {assert, fixture} from '@open-wc/testing';
|
|||
import {ChromedashIntentTemplate} from './chromedash-intent-template';
|
||||
|
||||
describe('chromedash-intent-template', () => {
|
||||
const validFeature = {
|
||||
id: 123456,
|
||||
name: 'feature one',
|
||||
summary: 'fake detailed summary',
|
||||
category: 'fake category',
|
||||
feature_type: 'fake feature type',
|
||||
feature_type_int: 0,
|
||||
new_crbug_url: 'fake crbug link',
|
||||
owner_emails: ['owner1@example.com', 'owner2@example.com'],
|
||||
explainer_links: [
|
||||
'https://example.com/explainer1',
|
||||
'https://example.com/explainer2',
|
||||
],
|
||||
blink_components: ['Blink'],
|
||||
interop_compat_risks: 'Some risks here',
|
||||
ongoing_constraints: 'Some constraints',
|
||||
finch_name: 'AFinchName',
|
||||
browsers: {
|
||||
chrome: {
|
||||
status: {
|
||||
milestone_str: 'No active development',
|
||||
text: 'No active development',
|
||||
val: 1,
|
||||
},
|
||||
},
|
||||
ff: {view: {text: 'No signal', val: 5}},
|
||||
safari: {view: {text: 'No signal', val: 5}},
|
||||
webdev: {view: {text: 'Positive', val: 1}},
|
||||
other: {view: {}},
|
||||
},
|
||||
stages: [
|
||||
{
|
||||
id: 1,
|
||||
stage_type: 110,
|
||||
intent_stage: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
stage_type: 150,
|
||||
intent_thread_url: 'https://example.com/experiment',
|
||||
desktop_first: 100,
|
||||
desktop_last: 106,
|
||||
android_first: 100,
|
||||
android_last: 106,
|
||||
webview_first: 100,
|
||||
webview_last: 106,
|
||||
extensions: [
|
||||
{
|
||||
id: 22,
|
||||
stage_type: 151,
|
||||
intent_thread_url: 'https://example.com/extend',
|
||||
desktop_last: 109,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
stage_type: 160,
|
||||
intent_thread_url: 'https://example.com/ship',
|
||||
desktop_first: 110,
|
||||
android_first: 110,
|
||||
webview_first: 110,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
stage_type: 160,
|
||||
},
|
||||
],
|
||||
browsers: {
|
||||
chrome: {
|
||||
blink_components: ['Blink'],
|
||||
owners: ['fake chrome owner one', 'fake chrome owner two'],
|
||||
status: {
|
||||
milestone_str: 'No active development',
|
||||
text: 'No active development',
|
||||
val: 1,
|
||||
},
|
||||
},
|
||||
ff: {view: {text: 'No signal', val: 5}},
|
||||
safari: {view: {text: 'No signal', val: 5}},
|
||||
webdev: {view: {text: 'No signal', val: 4}},
|
||||
other: {view: {}},
|
||||
},
|
||||
resources: {
|
||||
samples: ['fake sample link one', 'fake sample link two'],
|
||||
docs: ['fake doc link one', 'fake doc link two'],
|
||||
},
|
||||
standards: {
|
||||
maturity: {
|
||||
short_text: 'Incubation',
|
||||
text: 'Specification being incubated in a Community Group',
|
||||
val: 3,
|
||||
},
|
||||
status: {text: "Editor's Draft", val: 4},
|
||||
},
|
||||
tags: ['tag_one'],
|
||||
};
|
||||
const validGate = {
|
||||
id: 200,
|
||||
stage_id: 2,
|
||||
gate_type: 2,
|
||||
};
|
||||
|
||||
it('renders with fake data', async () => {
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[1]}
|
||||
.gate=${validGate}
|
||||
subject="A fake subject"
|
||||
intentBody="<div>A basic intent body</div>"
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
|
@ -123,332 +20,8 @@ describe('chromedash-intent-template', () => {
|
|||
'#email-subject-content'
|
||||
);
|
||||
const body = component.shadowRoot.querySelector('#email-body-content');
|
||||
const expectedBody = `Contact emails
|
||||
owner1@example.com, owner2@example.com
|
||||
|
||||
|
||||
Explainer
|
||||
https://example.com/explainer1
|
||||
https://example.com/explainer2
|
||||
|
||||
|
||||
Specification
|
||||
None
|
||||
|
||||
|
||||
Design docs
|
||||
fake doc link one
|
||||
fake doc link two
|
||||
|
||||
|
||||
Summary
|
||||
|
||||
fake detailed summary
|
||||
|
||||
|
||||
|
||||
|
||||
Blink component
|
||||
Blink
|
||||
|
||||
|
||||
Search tags
|
||||
tag_one
|
||||
|
||||
|
||||
TAG review
|
||||
None
|
||||
|
||||
|
||||
Risks
|
||||
|
||||
|
||||
|
||||
Interoperability and Compatibility
|
||||
|
||||
Some risks here
|
||||
|
||||
|
||||
|
||||
Gecko: No signal
|
||||
|
||||
WebKit: No signal
|
||||
|
||||
Web developers: No signal
|
||||
|
||||
Other signals:
|
||||
|
||||
|
||||
WebView application risks
|
||||
|
||||
Does this intent deprecate or change behavior of existing APIs, such that it has potentially high risk for Android WebView-based applications?
|
||||
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
Goals for experimentation
|
||||
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
Ongoing technical constraints
|
||||
Some constraints
|
||||
|
||||
|
||||
Debuggability
|
||||
|
||||
None
|
||||
|
||||
|
||||
|
||||
|
||||
Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, ChromeOS, Android, and Android WebView)?
|
||||
No
|
||||
|
||||
|
||||
|
||||
Is this feature fully tested by web-platform-tests?
|
||||
No
|
||||
|
||||
|
||||
Flag name on chrome://flags
|
||||
None
|
||||
|
||||
|
||||
Finch feature name
|
||||
AFinchName
|
||||
|
||||
|
||||
Non-finch justification
|
||||
None
|
||||
|
||||
|
||||
Requires code in //chrome?
|
||||
No
|
||||
|
||||
|
||||
Estimated milestones
|
||||
Shipping on desktop 110
|
||||
Origin trial desktop first 100
|
||||
Origin trial desktop last 106
|
||||
Origin trial extension end milestone 109
|
||||
Shipping on Android 110
|
||||
Origin trial Android first 100
|
||||
Origin trial Android last 106
|
||||
Shipping on WebView 110
|
||||
Origin trial WebView first 100
|
||||
Origin trial WebView last 106
|
||||
|
||||
|
||||
|
||||
Link to entry on Chrome Status Test
|
||||
http://localhost:8000/feature/123456?gate=200
|
||||
|
||||
|
||||
Links to previous Intent discussions
|
||||
Intent to Experiment: https://example.com/experiment
|
||||
Intent to Extend Experiment: https://example.com/extend
|
||||
Intent to Ship: https://example.com/ship
|
||||
|
||||
|
||||
|
||||
This intent message was generated by Chrome Status Test.`;
|
||||
|
||||
const expectedSubject = 'Intent to Experiment: feature one';
|
||||
assert.equal(body.innerText, expectedBody);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders deprecation plan intent', async () => {
|
||||
// Deprecation feature type.
|
||||
validFeature.feature_type_int = 3;
|
||||
// Deprecation plan gate type.
|
||||
validGate.gate_type = 5;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Intent to Deprecate and Remove: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders prototype intent', async () => {
|
||||
// New feature type.
|
||||
validFeature.feature_type_int = 0;
|
||||
// Prototype gate type.
|
||||
validGate.gate_type = 1;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Intent to Prototype: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders "Ready for Developer Testing" template', async () => {
|
||||
// New feature type.
|
||||
validFeature.feature_type_int = 0;
|
||||
// No gate or stage provided for DevTrial templates.
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Ready for Developer Testing: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders "Intent to Ship" template', async () => {
|
||||
// New feature type.
|
||||
validFeature.feature_type_int = 0;
|
||||
// Ship gate type.
|
||||
validGate.gate_type = 4;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Intent to Ship: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders "Intent to Extend Experiment" template', async () => {
|
||||
// New feature type.
|
||||
validFeature.feature_type_int = 0;
|
||||
// OT extension gate type.
|
||||
validGate.gate_type = 3;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[1].extensions[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Intent to Extend Experiment: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders "Request for Deprecation Trial" template', async () => {
|
||||
// Deprecation feature type.
|
||||
validFeature.feature_type_int = 3;
|
||||
// OT gate type.
|
||||
validGate.gate_type = 2;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[1].extensions[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Request for Deprecation Trial: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders "Intent to Extend Deprecation Trial" template', async () => {
|
||||
// Deprecation feature type.
|
||||
validFeature.feature_type_int = 3;
|
||||
// OT extension gate type.
|
||||
validGate.gate_type = 3;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[1].extensions[0]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Intent to Extend Deprecation Trial: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
});
|
||||
|
||||
it('renders PSA shipping template', async () => {
|
||||
// PSA feature type.
|
||||
validFeature.feature_type_int = 2;
|
||||
// Shipping gate type.
|
||||
validGate.gate_type = 4;
|
||||
const component = await fixture(
|
||||
html`<chromedash-intent-template
|
||||
appTitle="Chrome Status Test"
|
||||
.feature=${validFeature}
|
||||
.stage=${validFeature.stages[2]}
|
||||
.gate=${validGate}
|
||||
>
|
||||
</chromedash-intent-template>`
|
||||
);
|
||||
assert.exists(component);
|
||||
assert.instanceOf(component, ChromedashIntentTemplate);
|
||||
|
||||
const expectedSubject = 'Web-Facing Change PSA: feature one';
|
||||
const subject = component.shadowRoot.querySelector(
|
||||
'#email-subject-content'
|
||||
);
|
||||
assert.equal(subject.innerText, expectedSubject);
|
||||
assert.equal(body.innerText, 'A basic intent body');
|
||||
assert.equal(subject.innerText, 'A fake subject');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -37,14 +37,6 @@ class ChromedashPostIntentDialog extends LitElement {
|
|||
@property({type: Array<string>})
|
||||
ownerEmails = [];
|
||||
|
||||
static get properties() {
|
||||
return {};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...SHARED_STYLES,
|
||||
|
@ -101,7 +93,6 @@ class ChromedashPostIntentDialog extends LitElement {
|
|||
}
|
||||
|
||||
submitIntent() {
|
||||
console;
|
||||
// Make sure that the CC emails input is valid.
|
||||
const ccEmailsInput = this.shadowRoot!.querySelector('sl-input');
|
||||
if (!ccEmailsInput || ccEmailsInput.hasAttribute('data-user-invalid')) {
|
||||
|
@ -114,8 +105,7 @@ class ChromedashPostIntentDialog extends LitElement {
|
|||
submitButton.setAttribute('disabled', '');
|
||||
}
|
||||
window.csClient
|
||||
.postIntentToBlinkDev(this.featureId, {
|
||||
stage_id: this.stageId,
|
||||
.postIntentToBlinkDev(this.featureId, this.stageId, {
|
||||
gate_id: this.gateId,
|
||||
intent_cc_emails: ccEmailsInput?.value?.split(','),
|
||||
})
|
||||
|
|
|
@ -646,8 +646,11 @@ export class ChromeStatusClient {
|
|||
}
|
||||
|
||||
// Intents API
|
||||
async postIntentToBlinkDev(featureId, body) {
|
||||
return this.doPost(`/features/${featureId}/postintent`, body);
|
||||
async getIntentBody(featureId, stageId) {
|
||||
return this.doGet(`/features/${featureId}/${stageId}/intent`)
|
||||
}
|
||||
async postIntentToBlinkDev(featureId, stageId, body) {
|
||||
return this.doPost(`/features/${featureId}/${stageId}/intent`, body);
|
||||
}
|
||||
|
||||
// Progress API
|
||||
|
|
2
main.py
2
main.py
|
@ -137,6 +137,8 @@ api_routes: list[Route] = [
|
|||
reviews_api.XfnGatesAPI),
|
||||
Route(f'{API_BASE}/features/<int:feature_id>/postintent',
|
||||
intents_api.IntentsAPI),
|
||||
Route(f'{API_BASE}/features/<int:feature_id>/<int:stage_id>/intent',
|
||||
intents_api.IntentsAPI),
|
||||
|
||||
Route(f'{API_BASE}/blinkcomponents',
|
||||
blink_components_api.BlinkComponentsAPI),
|
||||
|
|
Загрузка…
Ссылка в новой задаче