UNPKG

qa-shadow-report

Version:

npm package that prints formatted test reports into a google sheet or csv file

614 lines (581 loc) 20.2 kB
import { batchUpdateMasterSheet } from '../google/googleSheetIntegration/writeToSheet.js'; import { findHeaderRowIndex } from '../monthlySummaryMethods/summaryGenerationHelpers.js'; import { solidBlackWidthOne, solidBlackWidthTwo } from './summaryStyles.js'; /** * Creates a grid style for a specific tab with defined borders. * This function generates an object that defines border styles for a grid in a Google Sheet. * It uses the payload from daily data and headers to determine the range and apply appropriate borders. * * @async * @param {number} destinationTabId - The ID of the target tab where the grid will be styled. * @param {Object} fullDailyPayload - An object containing daily payload data with body and header information. * @returns {Promise<Object>} - A promise that resolves to an object containing the border styles and the range to apply them. * @throws {Error} - Throws an error if the operation fails. */ const createGridStyle = async (destinationTabId, fullDailyPayload) => { const summaryHeaderRowStart = 0; const summaryHeaderColumnStart = 0; const headerRowIndex = findHeaderRowIndex(fullDailyPayload.bodyPayload); const endColumn = fullDailyPayload.bodyPayload[headerRowIndex].length; /** * Fetch the header and footer data for the given tab title. */ const payload = { updateBorders: { range: { sheetId: destinationTabId, startRowIndex: summaryHeaderRowStart, endRowIndex: fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length + 1, startColumnIndex: summaryHeaderColumnStart, endColumnIndex: endColumn, }, top: solidBlackWidthTwo, bottom: solidBlackWidthTwo, left: solidBlackWidthOne, right: solidBlackWidthOne, innerHorizontal: solidBlackWidthTwo, innerVertical: solidBlackWidthOne, }, }; return payload; }; /** * Adds the created grid style payload to the summary payload and sends the request to update the sheet. * The function first generates the grid style using `createGridStyle`, then it adds this style to the * summary payload's grid styles array. Finally, it sends a batch update request to the Google Sheets API * to apply the grid style to the specified sheet/tab. * * @async * @param {number} destinationTabId - The ID of the target tab where the grid will be styled. * @param {Object} fullDailyPayload - An object containing daily payload data including the summary grid styles. * @returns {Promise<void>} - A promise that resolves when the grid style has been successfully sent to the sheet. * @throws {Error} - Throws an error if there is an issue with applying the grid style to the sheet. */ export const sendGridStyle = async (destinationTabId, fullDailyPayload) => { const gridStyle = await createGridStyle(destinationTabId, fullDailyPayload); fullDailyPayload.summaryGridStyles.push(gridStyle); const requestBody = { requests: fullDailyPayload.summaryGridStyles, // This should be an array of requests }; try { await batchUpdateMasterSheet(requestBody); } catch (error) { console.error('sendGridStyle: An error occurred:', error); } }; /** * Creates a payload for a batch update to apply conditional formatting based on the state. * The function defines conditional formatting rules for the 'state' column in a sheet. * It uses a custom formula to apply a background color to cells matching a specific state. * * @param {number} sheetId - The ID of the sheet to apply formatting to. * @param {Object} fullDailyPayload - An object containing the daily payload data with body and header information. * @param {string} state - The state to check in the conditional formatting ('failed' or 'passed'). * @returns {Object} - The request payload for the batch update containing conditional format rules. * @example * const formattingPayload = createConditionalFormattingPayload(sheetId, fullDailyPayload, 'failed'); */ export const createConditionalFormattingPayload = ( sheetId, fullDailyPayload, state ) => { const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const targetIndex = headerRow.indexOf('state'); const endRow = fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length; const failedFormula = '=IF(I6:I107="failed",true)'; const passedFormula = '=IF(I6:I107="passed",true)'; const failedColor = { red: 1, green: 0.49803922, blue: 0.49803922 }; const passedColor = { red: 0.49803922, green: 1, blue: 0.49803922 }; let formula = passedFormula; let color = passedColor; if (state === 'failed') { formula = failedFormula; color = failedColor; } const conditionalFormatRule = { addConditionalFormatRule: { rule: { ranges: [ { sheetId: sheetId, startRowIndex: headerRowIndex, endRowIndex: endRow, startColumnIndex: targetIndex, endColumnIndex: targetIndex + 1, }, ], booleanRule: { condition: { type: 'CUSTOM_FORMULA', values: [{ userEnteredValue: formula }], }, format: { backgroundColor: color, }, }, }, }, }; return { requests: [conditionalFormatRule] }; }; /** * Constructs a request payload for freezing rows in a Google Sheet up to a specified row index. * This payload can then be used with the Google Sheets API to update the sheet properties. * * @param {number} sheetId - The ID of the individual sheet within the spreadsheet where rows are to be frozen. * @param {number} freezeTillRow - The number of top rows to freeze. Rows above this index will be frozen. * @returns {Promise<Object>} A batch update request payload containing the sheet properties update for freezing rows. * @example * const freezeRowsPayload = freezeRowsInSheet(12345, 2); * // The above call creates a payload to freeze the first two rows of the sheet with ID 12345. */ export const freezeRowsInSheet = async (sheetId, freezeTillRow) => { const requests = [ { updateSheetProperties: { properties: { sheetId: sheetId, gridProperties: { frozenRowCount: freezeTillRow, }, }, fields: 'gridProperties.frozenRowCount', }, }, ]; return { requests }; }; /** * Creates a request object for the Google Sheets API to repeat a cell's formatting * across a specified range. This function is typically used for applying a uniform * background color to a range of cells within a sheet. * * @param {number} sheetId - The ID of the sheet where the cells will be formatted. * @param {number} startRowIndex - The index of the first row of the range to format. * @param {number} endRowIndex - The index of the row after the last row of the range to format. * @param {number} columnLength - The number of columns to format, starting from the first column. * @param {Object} backgroundColor - An object representing the color to set as the cell background. * @returns {Object} A request object ready to be used in a batchUpdate to apply the formatting. * * @example * // Define a light grey color * const lightGrey = { red: 0.9, green: 0.9, blue: 0.9 }; * * // Create a repeatCell request for rows 1 to 3 inclusive, assuming the sheet has 5 columns * const repeatCellRequest = createRepeatCellRequest(sheetId, 0, 3, 5, lightGrey); * * // The returned request can be included in the requests array for batchUpdate */ const createRepeatCellRequest = ( sheetId, startRowIndex, endRowIndex, columnLength, backgroundColor ) => ({ repeatCell: { range: { sheetId, startRowIndex, endRowIndex, startColumnIndex: 0, endColumnIndex: columnLength, }, cell: { userEnteredFormat: { backgroundColor }, }, fields: 'userEnteredFormat.backgroundColor', }, }); /** * Constructs a payload to apply background colors to the header and footer rows * and all rows above the header of a Google Sheet. It assumes a specific number of columns * based on the header row length. * * @param {number} sheetId - The ID of the sheet to be updated. * @param {Object} fullDailyPayload - The data object containing the header and body payload, * used to determine row indices and column lengths. * @returns {Object} A payload object for the batch update request to format the sheet cells. * @example * const colorStylesPayload = buildColorStylesPayload(12345, fullDailyPayload); * // This call creates a payload to apply background colors to the header and * // all preceding rows in the sheet with ID 12345. */ export const buildColorStylesPayload = (sheetId, fullDailyPayload) => { const lightGrey = { red: 0.9, green: 0.9, blue: 0.9 }; // Light grey color (e.g., #D9D9D9) const darkGrey = { red: 0.75, green: 0.75, blue: 0.75 }; // Dark grey color (e.g., #A9A9A9) const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const footerRowIndex = fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length; const requests = [ // Apply light grey color to all cells above the header row createRepeatCellRequest( sheetId, 0, headerRowIndex, headerRow.length, lightGrey ), // Apply dark grey color to the header row createRepeatCellRequest( sheetId, headerRowIndex - 1, headerRowIndex, headerRow.length, darkGrey ), // Apply dark grey color to the header row createRepeatCellRequest( sheetId, footerRowIndex, footerRowIndex + 1, headerRow.length, darkGrey ), ]; return { requests }; }; /** * Builds a payload for a batch update to set the row heights for a specified range in a Google Sheet. * The function determines the range based on the provided data structure. * * @param {number} sheetId - The ID of the Google Sheet to update. * @param {Object} fullDailyPayload - An object containing the daily payload data which includes * header and body information to calculate row indices. * @returns {Object} A payload object structured for the Google Sheets API batchUpdate method, * which includes the request for updating row heights. */ export const buildRowHeightPayload = (sheetId, fullDailyPayload) => { const pixelSize = 27; // The desired height in pixels for the rows. // Calculate the starting and ending indices for the rows to adjust. const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const totalRows = fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length; // Create a request to update the row heights starting from the row after the header and // including all the body rows. const requests = [ { updateDimensionProperties: { range: { sheetId: sheetId, dimension: 'ROWS', startIndex: headerRowIndex, endIndex: headerRowIndex + totalRows, }, properties: { pixelSize: pixelSize, // Set each row to the specified height. }, fields: 'pixelSize', // Specify that only the pixelSize property should be updated. }, }, ]; return { requests }; // Return the constructed payload. }; export const BuildTextStyles = async (sheetId, fullDailyPayload) => { const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const footerRowIndex = fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length; // Define the format for the header row const headerAndFooterRowFormat = { userEnteredFormat: { textFormat: { bold: true, fontSize: 12 }, }, }; // Define the format for other rows const otherRowFormat = { userEnteredFormat: { textFormat: { bold: true, fontSize: 10 }, }, }; const requests = [ { // Apply bold and fontSize 12 to header row repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex - 1, endRowIndex: headerRowIndex, endColumnIndex: headerRow.length, }, cell: headerAndFooterRowFormat, fields: 'userEnteredFormat.textFormat', }, }, { // Apply bold and fontSize 12 to footer row repeatCell: { range: { sheetId: sheetId, startRowIndex: footerRowIndex, endRowIndex: footerRowIndex + 1, endColumnIndex: headerRow.length, }, cell: headerAndFooterRowFormat, fields: 'userEnteredFormat.textFormat', }, }, { // Apply bold and fontSize 10 to other header rows repeatCell: { range: { sheetId: sheetId, endRowIndex: headerRowIndex - 1, endColumnIndex: headerRow.length, }, cell: otherRowFormat, fields: 'userEnteredFormat.textFormat', }, }, ]; return { requests }; }; export const setTextWrappingToClip = async (sheetId, fullDailyPayload) => { const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const requests = [ { repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex, endRowIndex: fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length, endColumnIndex: headerRow.length, }, cell: { userEnteredFormat: { wrapStrategy: 'CLIP', }, }, fields: 'userEnteredFormat.wrapStrategy', }, }, ]; return { requests }; }; export const createTextAlignmentPayload = async (sheetId, fullDailyPayload) => { const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const footerRowIndex = fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length; const testNameTargetIndex = headerRow.indexOf('test name'); const specTargetIndex = headerRow.indexOf('spec'); const typeTargetIndex = headerRow.indexOf('type'); const teamTargetIndex = headerRow.indexOf('team'); const stateTargetIndex = headerRow.indexOf('state'); const requests = [ { repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex, endRowIndex: fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length, startColumnIndex: testNameTargetIndex, endColumnIndex: testNameTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'RIGHT', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, endRowIndex: headerRowIndex, startColumnIndex: specTargetIndex, endColumnIndex: specTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'LEFT', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, endRowIndex: headerRowIndex, startColumnIndex: typeTargetIndex, endColumnIndex: typeTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'LEFT', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, endRowIndex: headerRowIndex, startColumnIndex: stateTargetIndex, endColumnIndex: stateTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'CENTER', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex - 1, endRowIndex: headerRowIndex, endColumnIndex: headerRow.length, }, cell: { userEnteredFormat: { horizontalAlignment: 'CENTER', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex, endRowIndex: fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length, startColumnIndex: typeTargetIndex, endColumnIndex: typeTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'CENTER', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, startRowIndex: footerRowIndex, endRowIndex: footerRowIndex + 1, endColumnIndex: headerRow.length, }, cell: { userEnteredFormat: { horizontalAlignment: 'CENTER', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { repeatCell: { range: { sheetId: sheetId, endRowIndex: headerRowIndex - 1, startColumnIndex: teamTargetIndex, endColumnIndex: teamTargetIndex + 1, }, cell: { userEnteredFormat: { horizontalAlignment: 'LEFT', }, }, fields: 'userEnteredFormat.horizontalAlignment', }, }, { // Vertical alignment to the middle repeatCell: { range: { sheetId: sheetId, startRowIndex: headerRowIndex, endRowIndex: fullDailyPayload.bodyPayload.length + fullDailyPayload.headerPayload.length, endColumnIndex: specTargetIndex + 1, }, cell: { userEnteredFormat: { verticalAlignment: 'TOP', }, }, fields: 'userEnteredFormat.verticalAlignment', }, }, ]; return { requests }; }; export const setColumnWidths = async (sheetId, fullDailyPayload) => { const headerRowIndex = findHeaderRowIndex(fullDailyPayload.headerPayload); const headerRow = fullDailyPayload.headerPayload[headerRowIndex - 1]; const testNameTargetIndex = headerRow.indexOf('test name'); const categoryTargetIndex = headerRow.indexOf('category'); const statusTargetIndex = headerRow.indexOf('status'); const typeTargetIndex = headerRow.indexOf('type'); const teamTargetIndex = headerRow.indexOf('team'); const columnWidths = [ { endIndex: testNameTargetIndex + 1, width: 180, }, { startIndex: categoryTargetIndex, endIndex: categoryTargetIndex + 1, width: 160, }, { startIndex: statusTargetIndex, endIndex: statusTargetIndex + 1, width: 160, }, { startIndex: typeTargetIndex, endIndex: typeTargetIndex + 1, width: 120, }, { startIndex: teamTargetIndex, endIndex: teamTargetIndex + 1, width: 120, }, ]; const requests = columnWidths.map((columnWidth) => ({ updateDimensionProperties: { range: { sheetId: sheetId, dimension: 'COLUMNS', startIndex: columnWidth.startIndex, endIndex: columnWidth.endIndex, }, properties: { pixelSize: columnWidth.width, }, fields: 'pixelSize', }, })); return { requests }; };