mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Store nightly outcome and compute daily broken/recovered from Firebase (#53066)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53066 This diff introduces the mechanism for us to store the result of nightly 3p library integration on Firebase. Having the result store, we can now query the result from the previous day and report if the build is newly broken or recovered overnight. Changelog: [Internal] [Changed] - Reviewed By: cipolleschi Differential Revision: D79565536 fbshipit-source-id: ba839b2950462a7ca6186a163f93f062719304fb
This commit is contained in:
committed by
Facebook GitHub Bot
parent
aa4555eaf1
commit
f806851875
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
// We connect to firebase using a plain HTTP request because we don't want to
|
||||
// add yet another devDependency to the react-native monorepo.
|
||||
class FirebaseClient {
|
||||
constructor() {
|
||||
this.email = process.env.FIREBASE_APP_EMAIL;
|
||||
this.password = process.env.FIREBASE_APP_PASS;
|
||||
this.apiKey = process.env.FIREBASE_APP_APIKEY;
|
||||
this.projectId = process.env.FIREBASE_APP_PROJECTNAME;
|
||||
this.databaseUrl = `${this.projectId}-default-rtdb.firebaseio.com`;
|
||||
this.idToken = null;
|
||||
}
|
||||
|
||||
async authenticate() {
|
||||
if (!this.email || !this.password) {
|
||||
throw new Error(
|
||||
'Firebase credentials not found in environment variables',
|
||||
);
|
||||
}
|
||||
|
||||
const authData = {
|
||||
email: this.email,
|
||||
password: this.password,
|
||||
returnSecureToken: true,
|
||||
};
|
||||
|
||||
const response = await this.makeRequest(
|
||||
'identitytoolkit.googleapis.com',
|
||||
`/v1/accounts:signInWithPassword?key=${this.apiKey}`,
|
||||
'POST',
|
||||
authData,
|
||||
);
|
||||
|
||||
this.idToken = response.idToken;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a database request for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @param {string} method - HTTP method
|
||||
* @param {*} data - Data to send (optional)
|
||||
* @returns {Promise<*>} - Response data
|
||||
*/
|
||||
async makeDatabaseRequest(date, method, data = null) {
|
||||
if (!this.idToken) {
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
const path = `/nightly-results/${date}.json?auth=${this.idToken}`;
|
||||
return this.makeRequest(this.databaseUrl, path, method, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store test results for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @param {Array<Object>} results - Array of test results
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async storeResults(date, results) {
|
||||
await this.makeDatabaseRequest(date, 'PUT', results);
|
||||
console.log(`Successfully stored results for ${date}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve test results for a specific date
|
||||
* @param {string} date - Date in YYYY-MM-DD format
|
||||
* @returns {Promise<Array<Object>|null>} - Array of test results or null if not found
|
||||
*/
|
||||
async getResults(date) {
|
||||
try {
|
||||
return await this.makeDatabaseRequest(date, 'GET');
|
||||
} catch (error) {
|
||||
if (error.message.includes('404')) {
|
||||
return null; // No results found for this specific date.
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async makeRequest(hostname, path, method, data = null) {
|
||||
const url = `https://${hostname}${path}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage;
|
||||
try {
|
||||
const parsedError = JSON.parse(responseText);
|
||||
errorMessage = parsedError.error?.message || responseText;
|
||||
} catch {
|
||||
errorMessage = responseText;
|
||||
}
|
||||
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(responseText);
|
||||
} catch {
|
||||
return responseText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare current results with previous day's results
|
||||
* @param {Array<Object>} currentResults - Today's test results
|
||||
* @param {Array<Object>} previousResults - Yesterday's test results
|
||||
* @returns {Object} - Object containing broken and recovered tests
|
||||
*/
|
||||
function compareResults(currentResults, previousResults) {
|
||||
if (!previousResults) {
|
||||
return {
|
||||
broken: [],
|
||||
recovered: [],
|
||||
newFailures: currentResults.filter(result => result.status !== 'success'),
|
||||
};
|
||||
}
|
||||
|
||||
// Create maps for easier lookup
|
||||
const currentMap = new Map();
|
||||
const previousMap = new Map();
|
||||
|
||||
currentResults.forEach(result => {
|
||||
const key = `${result.library}-${result.platform}`;
|
||||
currentMap.set(key, result);
|
||||
});
|
||||
|
||||
previousResults.forEach(result => {
|
||||
const key = `${result.library}-${result.platform}`;
|
||||
previousMap.set(key, result);
|
||||
});
|
||||
|
||||
const broken = [];
|
||||
const recovered = [];
|
||||
|
||||
// Check for broken tests (was success, now failed)
|
||||
for (const [key, currentResult] of currentMap) {
|
||||
const previousResult = previousMap.get(key);
|
||||
if (previousResult) {
|
||||
if (
|
||||
previousResult.status === 'success' &&
|
||||
currentResult.status !== 'success'
|
||||
) {
|
||||
broken.push({
|
||||
library: currentResult.library,
|
||||
platform: currentResult.platform,
|
||||
previousStatus: previousResult.status,
|
||||
currentStatus: currentResult.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for recovered tests (was failed, now success)
|
||||
for (const [key, currentResult] of currentMap) {
|
||||
const previousResult = previousMap.get(key);
|
||||
if (previousResult) {
|
||||
if (
|
||||
previousResult.status !== 'success' &&
|
||||
currentResult.status === 'success'
|
||||
) {
|
||||
recovered.push({
|
||||
library: currentResult.library,
|
||||
platform: currentResult.platform,
|
||||
previousStatus: previousResult.status,
|
||||
currentStatus: currentResult.status,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {broken, recovered};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get yesterday's date in YYYY-MM-DD format
|
||||
* @returns {string} - Yesterday's date
|
||||
*/
|
||||
function getYesterdayDate() {
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
return yesterday.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get today's date in YYYY-MM-DD format
|
||||
* @returns {string} - Today's date
|
||||
*/
|
||||
function getTodayDate() {
|
||||
const today = new Date();
|
||||
return today.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FirebaseClient,
|
||||
compareResults,
|
||||
getYesterdayDate,
|
||||
getTodayDate,
|
||||
};
|
||||
Reference in New Issue
Block a user