Add a display name to trial and ship stages (#2871)
* Add a display name to trial and ship stages * Update trial extension name format * changes suggested by @jrobbins
This commit is contained in:
Родитель
428ca2e93f
Коммит
888ed87aa0
|
@ -173,6 +173,7 @@ def stage_to_json_dict(
|
|||
'id': stage.key.integer_id(),
|
||||
'feature_id': stage.feature_id,
|
||||
'stage_type': stage.stage_type,
|
||||
'display_name': stage.display_name,
|
||||
'intent_stage': INTENT_STAGES_BY_STAGE_TYPE.get(
|
||||
stage.stage_type, INTENT_NONE),
|
||||
'pm_emails': stage.pm_emails,
|
||||
|
|
|
@ -79,6 +79,7 @@ class StagesAPITest(testing_config.CustomTestCase):
|
|||
'announcement_url': None,
|
||||
'desktop_first': 100,
|
||||
'desktop_last': None,
|
||||
'display_name': None,
|
||||
'dt_milestone_android_start': None,
|
||||
'dt_milestone_desktop_start': None,
|
||||
'dt_milestone_ios_start': None,
|
||||
|
@ -175,6 +176,7 @@ class StagesAPITest(testing_config.CustomTestCase):
|
|||
'intent_thread_url': 'https://example.com/intent',
|
||||
'intent_to_experiment_url': 'https://example.com/intent',
|
||||
'desktop_first': 100,
|
||||
'display_name': None,
|
||||
'desktop_last': None,
|
||||
'android_first': None,
|
||||
'android_last': None,
|
||||
|
@ -227,6 +229,7 @@ class StagesAPITest(testing_config.CustomTestCase):
|
|||
'intent_to_experiment_url': 'https://example.com/intent',
|
||||
'intent_to_extend_experiment_url': None,
|
||||
'desktop_first': 100,
|
||||
'display_name': None,
|
||||
'desktop_last': None,
|
||||
'android_first': None,
|
||||
'android_last': None,
|
||||
|
|
|
@ -562,13 +562,16 @@ class ChromedashFeatureDetail extends LitElement {
|
|||
let numberDifferentiation = '';
|
||||
if (this.previousStageTypeRendered === feStage.stage_type) {
|
||||
this.sameTypeRendered += 1;
|
||||
numberDifferentiation = ` (${this.sameTypeRendered})`;
|
||||
numberDifferentiation = ` ${this.sameTypeRendered}`;
|
||||
} else {
|
||||
this.previousStageTypeRendered = feStage.stage_type;
|
||||
this.sameTypeRendered = 1;
|
||||
}
|
||||
|
||||
const name = `${processStage.name}${numberDifferentiation}`;
|
||||
let name = `${processStage.name}${numberDifferentiation}`;
|
||||
if (feStage.display_name) {
|
||||
name = `${processStage.name}: ${feStage.display_name}`;
|
||||
}
|
||||
const isActive = this.feature.active_stage_id === feStage.id;
|
||||
|
||||
// Show a button to add a trial extension stage for origin trial stages.
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import {LitElement, css, html, nothing} from 'lit';
|
||||
import {ref} from 'lit/directives/ref.js';
|
||||
import {showToastMessage, flattenSections, setupScrollToHash} from './utils.js';
|
||||
import {
|
||||
showToastMessage,
|
||||
flattenSections,
|
||||
setupScrollToHash,
|
||||
shouldShowDisplayNameField} from './utils.js';
|
||||
import './chromedash-form-table';
|
||||
import './chromedash-form-field';
|
||||
import {
|
||||
|
@ -146,21 +150,28 @@ export class ChromedashGuideEditallPage extends LitElement {
|
|||
return FORMS_BY_STAGE_TYPE[stageType] || null;
|
||||
}
|
||||
|
||||
renderStageSection(formattedFeature, name, feStage, stageFields) {
|
||||
renderStageSection(formattedFeature, sectionBaseName, feStage, stageFields) {
|
||||
if (!stageFields) return nothing;
|
||||
|
||||
// Add a number differentiation if this stage type is the same as another stage.
|
||||
let numberDifferentiation = '';
|
||||
if (this.previousStageTypeRendered && this.previousStageTypeRendered === feStage.stage_type) {
|
||||
this.sameTypeRendered += 1;
|
||||
numberDifferentiation = ` (${this.sameTypeRendered})`;
|
||||
numberDifferentiation = ` ${this.sameTypeRendered}`;
|
||||
} else {
|
||||
this.previousStageTypeRendered = feStage.stage_type;
|
||||
this.sameTypeRendered = 1;
|
||||
}
|
||||
const sectionName = `${name}${numberDifferentiation}`;
|
||||
|
||||
let sectionName = `${sectionBaseName}${numberDifferentiation}`;
|
||||
if (feStage.display_name) {
|
||||
sectionName = `${sectionBaseName}: ${feStage.display_name} `;
|
||||
}
|
||||
const formFieldEls = stageFields.map(field => {
|
||||
// Only show "display name" field if there is more than one stage of the same type.
|
||||
if (field === 'display_name' &&
|
||||
!shouldShowDisplayNameField(this.feature.stages, feStage.stage_type)) {
|
||||
return nothing;
|
||||
}
|
||||
let value = formattedFeature[field];
|
||||
if (STAGE_SPECIFIC_FIELDS.has(field)) {
|
||||
value = feStage[field];
|
||||
|
@ -226,9 +237,13 @@ export class ChromedashGuideEditallPage extends LitElement {
|
|||
const extensions = feStage.extensions || [];
|
||||
extensions.forEach(extensionStage => {
|
||||
fieldsOnly = flattenSections(FLAT_TRIAL_EXTENSION_FIELDS);
|
||||
let sectionName = FLAT_TRIAL_EXTENSION_FIELDS.name;
|
||||
if (feStage.display_name) {
|
||||
sectionName = ` ${FLAT_TRIAL_EXTENSION_FIELDS.name}: ${feStage.display_name} `;
|
||||
}
|
||||
formsToRender.push(this.renderStageSection(
|
||||
formattedFeature,
|
||||
`${FLAT_TRIAL_EXTENSION_FIELDS.name}`,
|
||||
sectionName,
|
||||
extensionStage,
|
||||
fieldsOnly));
|
||||
allFormFields = [...allFormFields, ...fieldsOnly];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {LitElement, css, html, nothing} from 'lit';
|
||||
import {ref} from 'lit/directives/ref.js';
|
||||
import {showToastMessage, setupScrollToHash} from './utils.js';
|
||||
import {setupScrollToHash, showToastMessage, shouldShowDisplayNameField} from './utils.js';
|
||||
import './chromedash-form-table';
|
||||
import './chromedash-form-field';
|
||||
import {
|
||||
|
@ -209,6 +209,11 @@ export class ChromedashGuideStagePage extends LitElement {
|
|||
feStage = this.stage;
|
||||
}
|
||||
return section.fields.map(field => {
|
||||
// Only show "display name" field if there is more than one stage of the same type.
|
||||
if (field === 'display_name' &&
|
||||
!shouldShowDisplayNameField(this.feature.stages, feStage.stage_type)) {
|
||||
return nothing;
|
||||
}
|
||||
const featureJSONKey = ALL_FIELDS[field].name || field;
|
||||
let value = formattedFeature[featureJSONKey];
|
||||
if (STAGE_SPECIFIC_FIELDS.has(featureJSONKey)) {
|
||||
|
|
|
@ -134,12 +134,15 @@ export class ChromedashGuideVerifyAccuracyPage extends LitElement {
|
|||
let numberDifferentiation = '';
|
||||
if (this.previousStageTypeRendered && this.previousStageTypeRendered === feStage.stage_type) {
|
||||
this.sameTypeRendered += 1;
|
||||
numberDifferentiation = ` (${this.sameTypeRendered})`;
|
||||
numberDifferentiation = ` ${this.sameTypeRendered}`;
|
||||
} else {
|
||||
this.previousStageTypeRendered = feStage.stage_type;
|
||||
this.sameTypeRendered = 1;
|
||||
}
|
||||
const sectionName = `${name}${numberDifferentiation}`;
|
||||
let sectionName = `${name}${numberDifferentiation}`;
|
||||
if (feStage.display_name) {
|
||||
sectionName = `${name}: ${feStage.display_name}`;
|
||||
}
|
||||
|
||||
const formFieldEls = stageFields.map(field => {
|
||||
let value = formattedFeature[field];
|
||||
|
@ -206,9 +209,13 @@ export class ChromedashGuideVerifyAccuracyPage extends LitElement {
|
|||
const extensions = feStage.extensions || [];
|
||||
extensions.forEach(extensionStage => {
|
||||
fieldsOnly = flattenSections(VERIFY_ACCURACY_TRIAL_EXTENSION_FIELDS);
|
||||
let sectionName = VERIFY_ACCURACY_TRIAL_EXTENSION_FIELDS.name;
|
||||
if (feStage.display_name) {
|
||||
sectionName = `${feStage.display_name} ${VERIFY_ACCURACY_TRIAL_EXTENSION_FIELDS.name}`;
|
||||
}
|
||||
formsToRender.push(this.renderStageSection(
|
||||
formattedFeature,
|
||||
`${VERIFY_ACCURACY_TRIAL_EXTENSION_FIELDS.name}`,
|
||||
sectionName,
|
||||
extensionStage,
|
||||
fieldsOnly));
|
||||
allFormFields = [...allFormFields, ...fieldsOnly];
|
||||
|
|
|
@ -261,12 +261,15 @@ export class ChromedashProcessOverview extends LitElement {
|
|||
let numberDifferentiation = '';
|
||||
if (this.previousStageTypeRendered === feStage.stage_type) {
|
||||
this.sameTypeRendered += 1;
|
||||
numberDifferentiation = ` (${this.sameTypeRendered})`;
|
||||
numberDifferentiation = ` ${this.sameTypeRendered}`;
|
||||
} else {
|
||||
this.previousStageTypeRendered = feStage.stage_type;
|
||||
this.sameTypeRendered = 1;
|
||||
}
|
||||
const sectionName = `${processStage.name}${numberDifferentiation}`;
|
||||
let sectionName = `${processStage.name}${numberDifferentiation}`;
|
||||
if (feStage.display_name) {
|
||||
sectionName = `${feStage.display_name} (${processStage.name})`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<tr class="${isActive ?
|
||||
|
|
|
@ -297,6 +297,7 @@ const FLAT_ORIGIN_TRIAL_FIELDS = {
|
|||
{
|
||||
name: 'Origin trial',
|
||||
fields: [
|
||||
'display_name',
|
||||
'experiment_goals',
|
||||
'experiment_risks',
|
||||
'ongoing_constraints',
|
||||
|
@ -360,6 +361,7 @@ const FLAT_PREPARE_TO_SHIP_FIELDS = {
|
|||
{
|
||||
name: 'Prepare to ship',
|
||||
fields: [
|
||||
'display_name',
|
||||
// Standardization
|
||||
'tag_review_status',
|
||||
'webview_risks',
|
||||
|
@ -523,6 +525,7 @@ const DEPRECATION_ORIGIN_TRIAL_FIELDS = {
|
|||
{
|
||||
name: 'Origin trial',
|
||||
fields: [
|
||||
'display_name',
|
||||
'experiment_goals',
|
||||
'experiment_risks',
|
||||
'ongoing_constraints',
|
||||
|
@ -557,6 +560,7 @@ const DEPRECATION_PREPARE_TO_SHIP_FIELDS = {
|
|||
{
|
||||
name: 'Prepare to ship',
|
||||
fields: [
|
||||
'display_name',
|
||||
'intent_to_ship_url',
|
||||
'i2s_lgtms',
|
||||
],
|
||||
|
|
|
@ -140,6 +140,7 @@ export const STAGE_SPECIFIC_FIELDS = new Set([
|
|||
'intent_to_extend_experiment_url',
|
||||
|
||||
// Misc fields.
|
||||
'display_name',
|
||||
'origin_trial_feedback_url',
|
||||
'finch_url',
|
||||
'experiment_goals',
|
||||
|
|
|
@ -1260,6 +1260,25 @@ export const ALL_FIELDS = {
|
|||
help_text: '',
|
||||
},
|
||||
|
||||
'display_name': {
|
||||
type: 'input',
|
||||
attrs: TEXT_FIELD_ATTRS,
|
||||
required: false,
|
||||
label: 'Stage display name',
|
||||
help_text: html`
|
||||
<p>Optional. Stage name to display on the feature detail page.</p>`,
|
||||
extra_help: html`
|
||||
<p>
|
||||
This name is only used for displaying stages on this site. Use this to differentiate stages of the same type.
|
||||
</p>
|
||||
<h4>Examples</h4>
|
||||
<ul>
|
||||
<li>Extended deprecation trial</li>
|
||||
<li>Second origin trial run</li>
|
||||
<li>Delayed ship for Android</li>
|
||||
</ul>`,
|
||||
},
|
||||
|
||||
'enterprise_policies': {
|
||||
type: 'input',
|
||||
attrs: MULTI_STRING_FIELD_ATTRS,
|
||||
|
|
|
@ -45,6 +45,24 @@ export function findProcessStage(feStage, process) {
|
|||
return null;
|
||||
}
|
||||
|
||||
/* Determine if the display name field should be displayed for a stage. */
|
||||
export function shouldShowDisplayNameField(feStages, stageType) {
|
||||
// The display name field is only available to a feature's stages
|
||||
// that have more than 1 of the same stage type associated.
|
||||
// It is used to differentiate those stages.
|
||||
let matchingStageCount = 0;
|
||||
for (let i = 0; i < feStages.length; i++) {
|
||||
if (feStages[i].stage_type === stageType) {
|
||||
matchingStageCount++;
|
||||
// If we find two of the same stage type, then display the display name field.
|
||||
if (matchingStageCount > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Given a process stage, find the first feature entry stage of the same type. */
|
||||
export function findFirstFeatureStage(intentStage, currentStage, fe) {
|
||||
if (intentStage == currentStage.intent_stage) {
|
||||
|
|
|
@ -226,6 +226,7 @@ class Stage(ndb.Model):
|
|||
# Identifying information: what.
|
||||
feature_id = ndb.IntegerProperty(required=True)
|
||||
stage_type = ndb.IntegerProperty(required=True)
|
||||
display_name = ndb.StringProperty()
|
||||
|
||||
# Pragmatic information: where and when.
|
||||
browser = ndb.StringProperty() # Blank or "Chrome" for now.
|
||||
|
|
|
@ -27,6 +27,7 @@ class StageDict(TypedDict):
|
|||
id: int
|
||||
feature_id: int
|
||||
stage_type: int
|
||||
display_name: str
|
||||
intent_stage: int
|
||||
intent_thread_url: str | None
|
||||
|
||||
|
|
|
@ -258,6 +258,7 @@ class FeatureEditHandler(basehandlers.FlaskHandler):
|
|||
('rollout_platforms', 'split_str'),
|
||||
('rollout_details', 'str'),
|
||||
('enterprise_policies', 'split_str'),
|
||||
('display_name', 'str')
|
||||
]
|
||||
|
||||
INTENT_FIELDS: list[str] = [
|
||||
|
|
Загрузка…
Ссылка в новой задаче