app/classes/ReleaseInsights/Release.php (163 lines of code) (raw):
<?php
declare(strict_types=1);
namespace ReleaseInsights;
use DateTime;
class Release
{
/** @var array<int> $no_planned_dot_releases */
public array $no_planned_dot_releases = [108, 111, 115, 120];
private readonly Version $version;
public function __construct(
string $version,
public readonly string $product_details = URL::ProductDetails->value,
)
{
$this->version = new Version($version);
}
/**
* Get The schedule for a future Release
*
* @return array<string, string>
*/
public function getSchedule(): array
{
if ($this->version->int < 1) {
return ['error' => 'Invalid version number.'];
}
if ($this->version->int < BETA) {
return $this->getPastSchedule();
}
return $this->getFutureSchedule();
}
/**
* Get The schedule for a future Release
*
* @return array<string, string>
*/
public function getFutureSchedule(): array
{
$all_releases = new Data($this->product_details)->getMajorReleases();
if (! array_key_exists($this->version->normalized, $all_releases)) {
return ['error' => 'Not enough data for this version number.'];
}
// Future release date object
$release = new DateTime($all_releases[$this->version->normalized] . ' 06:00 PST');
$nightly_target = Version::decrement($this->version->normalized, 2);
// Calculate 1st day of the nightly cycle
$nightly = new DateTime($all_releases[$nightly_target]);
$nightly->modify('-1 day');
// Transform all the DateTime objects in the $schedule array into formated date strings
$date = fn(string|object $day): string => is_object($day) ? $day->format('Y-m-d H:i:sP') : $nightly->modify($day)->format('Y-m-d H:i:sP');
$schedule = [
#️⃣ Starting with Firefox 136, QA request deadline is before the start of the nightly cycle
'qa_request_deadline' => $date($nightly->modify('-3 days')),
'nightly_start' => $date($nightly->modify('+3 days')),
'a11y_request_deadline' => $date('Friday'),
'qa_feature_done' => $date('Friday +1 week 21:00'),
'qa_test_plan_due' => $date('Friday 21:00'),
'soft_code_freeze' => $date($nightly->modify('+1 week')->modify('Thursday 08:00')),
'qa_pre_merge_done' => $date('Friday 14:00'),
'string_freeze' => $date('Friday'),
'merge_day' => $date('Monday'),
'beta_1' => $date('Monday'),
'beta_2' => $date('Wednesday 13:00'),
'beta_3' => $date('Friday 13:00'),
'sumo_1' => $date('Friday 21:00'), // Friday of Beta week 1
'beta_4' => $date('Monday 13:00'),
'beta_5' => $date('Wednesday 13:00'),
'beta_6' => $date('Friday 13:00'),
'beta_7' => $date('Monday 13:00'),
'sumo_2' => $date('Monday 21:00'), // Monday of Beta Week 3
'beta_8' => $date('Wednesday 13:00'),
'qa_pre_rc_signoff' => $date('Wednesday 14:00'),
'beta_9' => $date('Friday 13:00'),
'rc_gtb' => match ($this->version->normalized) {
'147.0' => $date($nightly->modify('+1 week')->modify('Monday 21:00')),
default => $date('Monday 21:00'),
},
'rc' => $date('Tuesday'),
'release' => $date($release->setTimezone(new \DateTimeZone('UTC'))),
];
// Add the Android weekly release before the planned dot release mid-cycle
if (! in_array($this->version->int, $this->no_planned_dot_releases)) {
$schedule += ['mobile_dot_release' => $date($release->modify('+1 week 00:00'))];
}
// Add the planned dot release mid-cycle
if (! in_array($this->version->int, $this->no_planned_dot_releases)) {
if ($this->version->normalized === '146.0') {
$schedule += ['planned_dot_release' => $date($release->modify('December 18 00:00'))];
} else {
$schedule += ['planned_dot_release' => $date($release->modify('+1 week 00:00'))];
}
}
// Sort the schedule by date, needed for schedules with a fixup
asort($schedule);
// Dev mode: assert that qa_pre_rc_signoff happens before the last beta
// If not, this causes a 1 week shift in rc_gtb calculation
$post_qa_step = array_search('qa_pre_rc_signoff', array_keys($schedule)) + 1;
assert(
str_starts_with(
array_keys($schedule)[$post_qa_step],
'beta'
) === true
);
// The schedule starts with the release version number
return ['version' => $this->version->normalized] + $schedule;
}
/**
* Get The schedule for a past Release
*
* @return array<string, string>
*/
public function getPastSchedule() : array
{
$data = new Data($this->product_details);
$releases = $data->getMajorReleases();
$release = new DateTime($releases[$this->version->normalized] . ' 06:00 PST');
$betas = $data->getPastBetas();
$betas = array_filter(
$betas,
fn($k) => str_starts_with($k, $this->version->normalized),
ARRAY_FILTER_USE_KEY
);
$dot_releases = $data->getDesktopPastReleases();
$dot_releases = array_filter(
$dot_releases,
fn($k) => $k != $this->version->normalized && str_starts_with($k, $this->version->normalized),
ARRAY_FILTER_USE_KEY
);
// Transform all the DateTime objects in the $schedule array into formated date strings
$format_date = fn(string $day): string => new DateTime($day)->format('Y-m-d H:i:sP');
$schedule = [
'nightly_start' => $format_date(Nightly::cycleStart($this->version->int)),
];
$count = 0;
foreach ($betas as $k => $date) {
$count++;
$schedule['beta_' . (string) $count] = $format_date($date);
}
$schedule += [
'release' => $release->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:sP'),
];
$count = 0;
foreach ($dot_releases as $date) {
$count++;
$schedule['dot_release_' . (string) $count] = $format_date($date);
}
// The schedule starts with the release version number
return ['version' => $this->version->normalized] + $schedule;
}
public static function getNiceLabel(string $version, string $label, bool $short=true): string
{
$short_version = (string) (int) $version;
$labels = [
'qa_request_deadline' => $short_version . ' QA request deadline',
'nightly_start' => 'Nightly ' . $short_version . ' starts',
'a11y_request_deadline' => $short_version . ' a11y engineering request deadline for Nightly',
'qa_feature_done' => $short_version .' build ready for QA', #️⃣ AKA Feature Complete Milestone
'qa_feature_done_1' => $short_version .' build ready for nightly QA',
'qa_feature_done_2' => $short_version .' build ready for beta QA',
'soft_code_freeze' => ($short ? '' : 'Firefox ') . $short_version . ' soft Code Freeze starts at 08:00 UTC',
'qa_pre_merge_done' => $short_version . ' regression testing completed',
'qa_test_plan_due' => 'Final deadline for QA’s Feature Test Plan approval',
'string_freeze' => 'String Freeze' . ($short ? '' : ' starts'),
'merge_day' => 'Merge day',
'beta_1' => ($short ? '' : 'Firefox ') . $short_version . ' b1 GTB',
'beta_2' => ($short ? '' : 'Firefox ') . $short_version . ' b2' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_3' => ($short ? '' : 'Firefox ') . $short_version . ' b3' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'sumo_1' => 'SUMO deadline',
'beta_4' => ($short ? '' : 'Firefox ') . $short_version . ' b4' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_5' => ($short ? '' : 'Firefox ') . $short_version . ' b5' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_6' => ($short ? '' : 'Firefox ') . $short_version . ' b6' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_7' => ($short ? '' : 'Firefox ') . $short_version . ' b7' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'sumo_2' => 'SUMO content localization starts',
'beta_8' => ($short ? '' : 'Firefox ') . $short_version . ' b8' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'qa_pre_rc_signoff' => $short_version . ' QA sign off',
'beta_9' => ($short ? '' : 'Firefox ') . $short_version . ' b9' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_10' => ($short ? '' : 'Firefox ') . $short_version . ' b10' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_11' => ($short ? '' : 'Firefox ') . $short_version . ' b11' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_12' => ($short ? '' : 'Firefox ') . $short_version . ' b12' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_13' => ($short ? '' : 'Firefox ') . $short_version . ' b13' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_14' => ($short ? '' : 'Firefox ') . $short_version . ' b14' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'beta_15' => ($short ? '' : 'Firefox ') . $short_version . ' b15' . ($short ? ' GTB' : ' (GTB: 13:00 UTC)'),
'rc_gtb' => ($short ? '' : 'Firefox ') . $short_version . ' go to Build',
'rc' => ($short ? '' : 'Firefox ') . 'RC',
'release' => ($short ? '' : 'Firefox ') . $short_version . ($short ? ' Release' : ' go-live @ 6AM PT'),
'mobile_dot_release' => ($short ? 'Potential mobile ' : 'Potential mobile ') . $version . ($short ? '.x' : ' dot release'),
'planned_dot_release' => ($short ? 'Planned ' : 'Planned Firefox ') . $version . ($short ? '.x' : ' dot release'),
];
return $labels[$label];
}
}