app/classes/ReleaseInsights/Data.php (142 lines of code) (raw):
<?php
declare(strict_types=1);
namespace ReleaseInsights;
class Data
{
/** @var array<string, string> $future_releases */
private readonly array $future_releases;
/** @var array<string, string> $release_owners */
private(set) array $release_owners;
/** @var array<string> $wellness_days */
private(set) array $wellness_days;
/** @var array<string> $chemspills */
private(set) array $chemspills;
public function __construct(
private readonly string $pd_url = URL::ProductDetails->value,
public int $cache_duration = 900 // 15 minutes
) {
$this->release_owners = include DATA . 'release_owners.php';
$this->future_releases = include DATA . 'upcoming_releases.php';
$this->wellness_days = include DATA . 'wellness_days.php';
$this->chemspills = include DATA . 'chemspill_releases.php';
}
/** @return array<string, string> */
public function getFutureReleases(): array
{
return array_filter(
$this->future_releases,
fn(string $key) => (int) $key > RELEASE,
ARRAY_FILTER_USE_KEY
);
}
/** @return array<string, string> */
public function getESRReleases(): array
{
// Historical data from Product Details, cache a week
$esr_releases = Json::load($this->pd_url . 'firefox.json', $this->cache_duration)['releases'];
// Reduce to only ESR releases
$esr_releases = array_filter(
$esr_releases,
fn(string $key) => str_ends_with($key, 'esr'),
ARRAY_FILTER_USE_KEY
);
// Rebuild a version_number => date array
$esr_releases = array_column($esr_releases, 'date', 'version');
// Sort releases by release date
asort($esr_releases);
return $esr_releases;
}
/**
* Get the release date of our Latest Major release
*
* @return array<string, string>
*/
public function getLatestMajorRelease(): array
{
$past_releases = $this->getMajorPastReleases();
$last_release = array_key_last($past_releases);
return [$last_release => $past_releases[$last_release]];
}
/**
* Get all past Desktop Releases on the release channel,
*
* @return array<string, string>
*/
public function getDesktopPastReleases(bool $dot_releases = true): array
{
// Historical data from Product Details, cache a week
$major_releases = Json::load($this->pd_url . 'firefox_history_major_releases.json', $this->cache_duration);
$minor_releases = $dot_releases == true ? Json::load($this->pd_url . 'firefox_history_stability_releases.json', $this->cache_duration) : [];
$all_releases = [...$major_releases, ...$minor_releases];
// Sort releases by release date
asort($all_releases);
// Remove all minor ESR releases
$exclude_esr = function (string $version_number) {
// Those releases were not ESR releases despite the middle number
if (in_array($version_number, ['33.1', '33.1.1', '50.1.0'])) {
return true;
}
$data = explode('.', $version_number);
// We started ESR releases with version 10
if (intval($data[0]) < 10) {
return true;
}
if (intval($data[1]) > 0) {
return false;
}
return true;
};
return array_filter($all_releases, $exclude_esr, ARRAY_FILTER_USE_KEY);
}
/**
* Get all Dot Releases for both Desktop and Android
* We ignore Android dot releases before 126
*
* @return array<string, array<string,string>>
*/
public function getDotReleases(): array
{
$filter = function(string $platform) {
$target = $platform == 'desktop' ? 'firefox.json' : 'mobile_android.json';
// Get source of Data where we can extract dot releases information
$data = Json::load($this->pd_url . $target, $this->cache_duration)['releases'];
// Extract all minor releases
$data = array_filter($data, fn($v) => isset($v['category']) && $v['category'] == 'stability');
if ($platform === 'desktop') {
// Filter out ESR releases
$data = array_filter($data, fn($k) => ! str_ends_with($k, 'esr'), ARRAY_FILTER_USE_KEY);
}
// Rebuild a simplified array: ['128.0.1' => '2024-07-16',...]
$data = array_column($data, 'date', 'version');
if ($platform === 'android') {
// Filter out versions older than 126.
// 126 is when we merged android and desktop code and aligned dot release naming.
$data = array_filter($data, fn($k) => explode('.', $k)[0] > 125, ARRAY_FILTER_USE_KEY);
}
return $data;
};
$desktop = $filter('desktop');
$android = $filter('android');
$all = array_merge($desktop, $android);
ksort ($all, SORT_NATURAL);
$dot_releases = [];
foreach ($all as $version => $date) {
$platform = 'both';
if (! array_key_exists($version, $android)) {
$platform = 'desktop';
}
if (! array_key_exists($version, $desktop)) {
$platform = 'android';
}
$dot_releases[$version] = ['date' => $date, 'platform' => $platform];
}
return $dot_releases;
}
/**
* Get all past Betas
*
* @return array<string, string>
*/
public function getPastBetas(): array
{
return Json::load($this->pd_url . 'firefox_history_development_releases.json', $this->cache_duration);
}
/**
* Get all past Releases on the release channel, but not dot releases
*
* @return array<string, string>
*/
public function getMajorPastReleases(): array
{
return Json::load($this->pd_url . 'firefox_history_major_releases.json', $this->cache_duration);
}
/**
* Get all past and planned Releases on the release channel, but not dot releases
*
* @return array<string, string>
*/
public function getMajorReleases(): array
{
return [
...$this->getMajorPastReleases(),
...$this->future_releases,
];
}
/** @return array<string, string> */
public function getFirefoxVersions(): array
{
// Cache Product Details versions, 15mn cache
return Json::load($this->pd_url . 'firefox_versions.json', $this->cache_duration);
}
public static function getDesktopAdoptionRate(string $version): ?float
{
// Check current uptake rate for the latest release*
// @codeCoverageIgnoreStart
if (! defined('TESTING_CONTEXT')) {
$uptake = Json::load(URL::Pollbot->value
. 'firefox/'
. $version
. '/telemetry/main-summary-uptake')['message'] ?? '0' ;
} else {
// @codeCoverageIgnoreEnd
if ($version == '130.0') {
$uptake = Json::load(URL::Pollbot->target() . 'main-summary-uptake.json')['message'] ?? '0' ;
} else {
$uptake = 'Query results contained no rows.';
}
}
if ($uptake == 'Query results contained no rows.') {
return null;
}
// This public data is stored as a string, extract only the number
$uptake = preg_replace('/Telemetry uptake for version.*\(.*\) is /', '', $uptake);
$uptake = str_replace('%', '', $uptake);
return (float) $uptake;
}
/**
* On Release day we have a lot of special cases.
*/
public function isTodayReleaseDay(): bool
{
return in_array(date('Y-m-d'), $this->getMajorReleases());
}
}