fylgja/src/blocks/sigma/sigma_search_results_block.js
2025-04-19 14:15:12 -04:00

209 lines
No EOL
6.6 KiB
JavaScript

/**
* 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
};