/** * sigma_search_results_block.js * * Generates Slack Block Kit blocks for displaying Sigma rule search results * Includes pagination controls for navigating large result sets * */ const logger = require('../../utils/logger'); const { getFileName } = require('../../utils/file_utils'); const { getProductEmoji } = require('../../utils/os_emojis'); const FILE_NAME = getFileName(__filename); /** * Generate blocks for Slack UI to display search results with pagination * * @param {string} keyword - The search keyword used for the query * @param {Array} results - Array of rule results from the search * @param {Object} pagination - Pagination information object * @returns {Array} - Slack blocks for displaying results */ const getSearchResultBlocks = (keyword, results, pagination = {}) => { logger.debug(`${FILE_NAME}: Creating search result blocks for keyword: "${keyword}"`); // Add debug for input validation logger.debug(`${FILE_NAME}: Results type: ${typeof results}, isArray: ${Array.isArray(results)}, length: ${Array.isArray(results) ? results.length : 'N/A'}`); logger.debug(`${FILE_NAME}: Pagination: ${JSON.stringify(pagination)}`); // Ensure results is always an array const safeResults = Array.isArray(results) ? results : []; // Default pagination values if not provided const pagingInfo = { currentPage: pagination.currentPage || 1, pageSize: pagination.pageSize || 10, totalPages: pagination.totalPages || 0, totalResults: pagination.totalResults || 0, hasMore: pagination.hasMore || false }; logger.debug(`${FILE_NAME}: Processing ${safeResults.length} search results (page ${pagingInfo.currentPage} of ${pagingInfo.totalPages}, total: ${pagingInfo.totalResults})`); // Initialize with header block that includes pagination info const blocks = [ { "type": "section", "text": { "type": "mrkdwn", "text": `*Search Results for "${keyword}"*\n${ pagingInfo.totalResults > 0 ? `Showing ${safeResults.length} of ${pagingInfo.totalResults} matching rules (page ${pagingInfo.currentPage} of ${pagingInfo.totalPages})` : `Found ${safeResults.length} matching rules:` }` } } ]; // Debug log as we build blocks logger.debug(`${FILE_NAME}: Added header block`); // Add blocks for each result if we have any if (safeResults.length === 0) { logger.debug(`${FILE_NAME}: No search results to display`); blocks.push({ "type": "section", "text": { "type": "mrkdwn", "text": pagingInfo.totalResults > 0 ? "No rules on this page. Try a different page." : "No matching rules found." } }); } else { logger.debug(`${FILE_NAME}: Creating blocks for ${safeResults.length} search results`); safeResults.forEach((rule, index) => { // Ensure rule is an object with expected properties const safeRule = rule || {}; const ruleId = safeRule.id || 'unknown'; logger.debug(`${FILE_NAME}: Adding result #${index + 1}: ${ruleId} - ${safeRule.title || 'Untitled'}`); // Get product emoji const osEmoji = getProductEmoji(safeRule.logsource && safeRule.logsource.product); // Rule information and action button - with OS emoji before title and no ID field blocks.push({ "type": "section", "text": { "type": "mrkdwn", "text": `*${osEmoji}${safeRule.title || 'Untitled Rule'}*` }, "accessory": { "type": "button", "text": { "type": "plain_text", "text": "Details", "emoji": true }, "value": ruleId, "action_id": "view_rule_details" } }); // Add a divider between results (except after the last one) if (index < safeResults.length - 1) { blocks.push({ "type": "divider" }); } }); } // Debug log for pagination controls logger.debug(`${FILE_NAME}: Checking if pagination controls needed (totalPages: ${pagingInfo.totalPages})`); // Add pagination navigation if there are multiple pages if (pagingInfo.totalPages > 1) { // Add a divider before pagination controls blocks.push({ "type": "divider" }); // Create pagination navigation buttons const paginationButtons = []; // Previous page button (if not on first page) if (pagingInfo.currentPage > 1) { paginationButtons.push({ "type": "button", "text": { "type": "plain_text", "text": "Previous", "emoji": true }, "value": JSON.stringify({ keyword, page: pagingInfo.currentPage - 1, pageSize: pagingInfo.pageSize }), "action_id": "search_prev_page" }); logger.debug(`${FILE_NAME}: Added Previous page button for page ${pagingInfo.currentPage - 1}`); } // Next page button (if there are more pages) if (pagingInfo.hasMore) { paginationButtons.push({ "type": "button", "text": { "type": "plain_text", "text": "Next", "emoji": true }, "value": JSON.stringify({ keyword, page: pagingInfo.currentPage + 1, pageSize: pagingInfo.pageSize }), "action_id": "search_next_page" }); logger.debug(`${FILE_NAME}: Added Next page button for page ${pagingInfo.currentPage + 1}`); } // Add the pagination buttons block if we have buttons to show if (paginationButtons.length > 0) { blocks.push({ "type": "actions", "elements": paginationButtons }); logger.debug(`${FILE_NAME}: Added ${paginationButtons.length} pagination buttons`); } // Add page indicator text blocks.push({ "type": "context", "elements": [ { "type": "plain_text", "text": `Page ${pagingInfo.currentPage} of ${pagingInfo.totalPages}`, "emoji": true } ] }); logger.debug(`${FILE_NAME}: Added page indicator text`); } logger.debug(`${FILE_NAME}: Created ${blocks.length} blocks for search results`); // Final validation of blocks array if (!Array.isArray(blocks) || blocks.length === 0) { logger.error(`${FILE_NAME}: Generated blocks is not a valid array or is empty`); // Return a minimal valid blocks array return [ { "type": "section", "text": { "type": "mrkdwn", "text": `Search Results for "${keyword}": Unable to generate proper blocks. Please try again.` } } ]; } return blocks; }; module.exports = { getSearchResultBlocks };