Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save NikhilP97/cdbb64e62803c550df4a2606334ce1a8 to your computer and use it in GitHub Desktop.
Save NikhilP97/cdbb64e62803c550df4a2606334ce1a8 to your computer and use it in GitHub Desktop.
Downloads the table details of app store testflight as a CSV file on your disk
/**
* This JS script downloads user details from test flight into a CSV File.
* Run each step at a time and not all together.
* Please read instructions for step 1.
*/
// ---------------Step 0 starts---------------
/**
* CONFIGURATION
*/
const APP_CONFIGURATION = {
/**
* To add additional columns, inspect the DOM and check how to identify the cell uniquely.
* In the case of email, there is an <a> tag inside which the text is present, hence we use that as the html query.
* For status and name, there is only one <span> tag inside which the text is present.
* For session, crashes, feedback, the text is inside a <div> tag and hence we cannot use it in the query.
* Hence we use the class 'tester-metrics-cell-text' to identify the enclosing text tag.
* This code depends on the current DOM structure to work.
* In the future, if the column positions are shifted, if the class names are changed or if the html tags are changed,
* make the changes to the below configuration accordingly.
*/
columnDetails: [
{ name: 'email', columnNo: 0, htmlQuery: 'a' },
{ name: 'status', columnNo: 2, htmlQuery: 'span' },
// { name: 'name', columnNo: 1, htmlQuery: 'span' },
// { name: 'sessions', columnNo: 3, htmlQuery: '.tester-metrics-cell-text' },
// { name: 'crashes', columnNo: 4, htmlQuery: '.tester-metrics-cell-text' },
// { name: 'feedback', columnNo: 5, htmlQuery: '.tester-metrics-cell-text' },
],
csvFileName: 'App-ios-test-flight-details.csv',
};
/**
* GLOBAL VARIABLES
*/
const APP_GLOBAL_VARS = {
userData: [],
emailSet: new Set(),
csvStr: '',
};
console.log('Successfully initialized variables!!!');
// ---------------Step 0 ends---------------
// ---------------Step 1 starts---------------
/**
* Step 1 - Find and store data in the global variables.
* IMPORTANT:
*
* Run this step multiple times until the length of the data is equal to the table length.
* This needs to be done because react virtualization library is being used which does not
* render all the information on the DOM at one time.
* So keep scrolling and run this script until all the data is captured.
*
*/
((APP_GLOBAL_VARS, APP_CONFIGURATION) => {
const { userData, emailSet } = APP_GLOBAL_VARS;
// details about the columns in the table
const { columnDetails } = APP_CONFIGURATION;
const emailColumn = columnDetails.find(column => column.name === 'email');
// iterate through each row and store details
document.querySelector('.ReactVirtualized__Grid__innerScrollContainer').childNodes.forEach((rowNode) => {
const email = rowNode.childNodes[emailColumn.columnNo].querySelector(emailColumn.htmlQuery).textContent;
// if email already exists, continue
if (emailSet.has(email)) return;
// else store the email in the set & user data array
emailSet.add(email);
const newUserDetails = {};
for (const column of columnDetails) {
const { name, columnNo, htmlQuery } = column;
const colNode = rowNode.childNodes[columnNo].querySelector(htmlQuery);
const textValue = colNode ? colNode.textContent : '';
newUserDetails[name] = textValue;
}
userData.push(newUserDetails);
});
console.log('No of users captured: ', userData.length);
})(APP_GLOBAL_VARS, APP_CONFIGURATION);
// ---------------Step 1 ends---------------
// ---------------Step 2 starts---------------
/**
* Step 2 - Transform array of data to comma separated values.
*/
APP_GLOBAL_VARS.csvStr = ((APP_GLOBAL_VARS) => {
const { userData } = APP_GLOBAL_VARS;
if (userData.length === 0) throw new Error('No data captured, run step 1 again');
// CSV header row
const HEADER = Object.keys(userData[0]).map(keyName => keyName.toUpperCase()).join(',');
// extra space to leave a line after header
const CSV_HEADER = `${HEADER}\n\n`;
// CSV values
const commaSeparatedValues = userData.map(details => {
const objectArray = Object.values(details);
return objectArray.join(',');
});
const lineSeparatedStr = commaSeparatedValues.join('\n');
console.log('Successfully created CSV string!!!');
return CSV_HEADER + lineSeparatedStr;
})(APP_GLOBAL_VARS);
// ---------------Step 2 ends---------------
// ---------------Step 3 starts---------------
/**
* Step 3 - Download CSV file to disk
*/
((APP_GLOBAL_VARS, APP_CONFIGURATION) => {
// load variables
const { csvStr } = APP_GLOBAL_VARS;
const { csvFileName } = APP_CONFIGURATION;
// transform to csv encoded string
const encodedUri = encodeURI(`data:text/csv;charset=utf-8,${csvStr}`);
var link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', csvFileName);
document.body.appendChild(link);
// This will download the csv file
link.click();
console.log('Successfully downloaded CSV file!!!');
})(APP_GLOBAL_VARS, APP_CONFIGURATION);
// ---------------Step 3 ends---------------
@MACwayne
Copy link

MACwayne commented Mar 11, 2022

Any tips to get past this error in Step 1

VM5190:21 Uncaught TypeError: Cannot read properties of undefined (reading 'columnNo')
    at <anonymous>:21:54
    at NodeList.forEach (<anonymous>)
    at <anonymous>:20:88
    at <anonymous>:39:3

@njarecki
Copy link

this script is amazing! thank you!!!!! apple has put the date of install in the second row of the status column. can you suggest a code snippet to capture that as well? would be a lifesaver. nick

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment