We use KIbana dashboard but the older version 7.10. On discover plugin when we keep manipulating filters, after certain time browser crashes with out of memory error.
I did my investigation and found out that cell.html file when commented out stops the memory leak. This cell is rendered for each column in each row. How to destroy it properly so OOM issue can be resolved.
cell.html:
<%
var attributes = '';
if (timefield) {
attributes='class="eui-textNoWrap" width="1%"';
} else if (sourcefield) {
attributes='class="eui-textBreakAll eui-textBreakWord"';
} else {
attributes='class="kbnDocTableCell__dataField eui-textBreakAll eui-textBreakWord"';
}
%>
<td style="cursor: pointer;" <%=attributes %> data-test-subj="docTableField">
<%= formatted %>
<span class="kbnDocTableCell__filter">
<% if (filterable) { %>
<button
ng-click="inlineFilter($event, '+')"
class="kbnDocTableRowFilterButton"
data-column="<%- column %>"
tooltip-append-to-body="1"
data-test-subj="docTableCellFilter"
tooltip="{{ ::'discover.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}"
tooltip-placement="bottom"
aria-label="{{ ::'discover.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}"
><icon type="'plusInCircle'" size="'s'" color="'primary'"></icon></button>
<button
ng-click="inlineFilter($event, '-')"
class="kbnDocTableRowFilterButton"
data-column="<%- column %>"
data-test-subj="docTableCellFilterNegate"
tooltip="{{ ::'discover.docTable.tableRow.filterOutValueButtonTooltip' | i18n: {defaultMessage: 'Filter out value'} }}"
aria-label="{{ ::'discover.docTable.tableRow.filterOutValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter out value'} }}"
tooltip-append-to-body="1"
tooltip-placement="bottom"
><icon type="'minusInCircle'" size="'s'" color="'primary'"></icon></button>
<% } %>
<button
id="btnPlayVoice"
ng-click="openContent($event)"
class="fa kbnDocTableRowFilterButton"
tooltip="{{viewPlay.label}}"
tooltip-append-to-body="1"
ng-show="viewPlay"
ng-class="viewPlay.class"
ng-disabled=disabledViewPlay
></button>
<button id="btnViewRemarks" ng-click="openRemarksContent($event)" class="kbnDocTableRowFilterButton"
tooltip="View Remarks" tooltip-append-to-body="1">
<icon type="'editorComment'" size="'s'" color="'primary'"></icon>
</button>
<button id="btnViewTranslator" ng-click="openTranslatorContent($event)" class="kbnDocTableRowFilterButton"
tooltip="Text Translation" tooltip-append-to-body="1" ng-show="translated_text_enabled">
<icon type="'document'" size="'s'" color="'primary'"></icon>
</button>
<button id="btnUnreadRecord" ng-if="row._source.acknowledged_by" ng-click="unreadRecord($event)" class="kbnDocTableRowFilterButton"
tooltip="Mark As Unread" tooltip-append-to-body="1">
<icon type="'returnKey'" size="'s'" color="'primary'"></icon>
</button>
<button id="btnViewRemarks" ng-click="openCaptureFilter($event)" class="kbnDocTableRowFilterButton"
tooltip="Capture Filters" tooltip-append-to-body="1" ng-show="data_grid_capture_enabled">
<icon type="'bullseye'" size="'s'" color="'primary'"></icon>
</button>
<button id="btnLID" ng-click="LID($event)" class="kbnDocTableRowFilterButton"
tooltip="LID" tooltip-append-to-body="1" ng-show="lid_manual_testing_enabled">
<!-- <icon type="'visText'" size="'s'" color="'primary'"></icon> -->
<span class="kuiIcon fa-buysellads"></span>
</button>
<button id="btnSID" ng-click="SID($event)" class="fas kbnDocTableRowFilterButton"
tooltip="SID" tooltip-append-to-body="1" ng-show="sid_manual_testing_enabled">
<span class="kuiIcon fa-microphone"></span>
</button>
<div>
<button id="processingCaption" class="fas kbnDocTableRowFilterButton" ng-show='disabledViewPlay' ng-disabled=true>
Processing...
</button>
</div>
<% if(column =='payload.http_get'||column=='payload.http_refer'||column =='payload.attribute_value') { %>
<button id="btnViewPIISPDI" ng-click="openPIISPDIContent($event)" class="kbnDocTableRowFilterButton"
tooltip="View PII Information" tooltip-append-to-body="1" data-column="<%- column %>">
<icon type="'dataVisualizer'" size="'s'" color="'primary'"></icon>
</button>
<% } %>
</span>
</td>
Function in table_row.ts which is responsible for handling cell.html:
function createSummaryRow(row: any) {
const indexPattern = $scope.indexPattern;
$scope.flattenedRow = indexPattern.flattenHit(row);
$scope.reconFilename = $scope.flattenedRow[
getServices().configVars.metafields.recon_file_name
]
? $scope.flattenedRow[getServices().configVars.metafields.recon_file_name][0]
: null;
$scope.fileMimeType = mime.lookup($scope.reconFilename);
$scope.disabledViewPlay = false;
$scope.viewPlay = false;
$scope.sid_manual_testing_enabled = false;
$scope.lid_manual_testing_enabled = false;
$scope.translated_text_enabled = false;
if (
getServices().configVars.config.menu_enabled.text_translation_enabled &&
$scope.flattenedRow.text
) {
$scope.translated_text_enabled = true;
}
if ($scope.reconFilename) {
if (/audio/.test($scope.fileMimeType) || /video/.test($scope.fileMimeType)) {
$scope.viewPlay = { class: 'fa-play', label: 'Play' };
if (getServices().configVars.config.lid_sid.sid_manual_testing_enabled) {
$scope.sid_manual_testing_enabled = true;
}
if (getServices().configVars.config.lid_sid.lid_manual_testing_enabled) {
$scope.lid_manual_testing_enabled = true;
}
} else {
$scope.viewPlay = { class: 'fa-eye', label: 'View' };
}
}
// We just create a string here because its faster.
// const newHtmls = [openRowHtml, selectRowHtml];
// const newHtmls = [openRowHtml, selectRowHtml, $scope.isProcessedByUser === 1 ? processedByUserHtml : '<td></td>'];
// const newHtmls = [openRowHtml];
const newHtmls = getServices().configVars.config.menu_enabled.datagrid_flyout_enabled
? [selectRowHtml, $scope.isProcessedByUser === 1 ? processedByUserHtml : '<td></td>']
: [openRowHtml, selectRowHtml, $scope.isProcessedByUser === 1 ? processedByUserHtml : '<td></td>'];
const mapping = indexPattern.fields.getByName;
const hideTimeColumn = getServices().uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false);
if (indexPattern.timeFieldName && !hideTimeColumn) {
newHtmls.push(
cellTemplate({
timefield: true,
formatted: _displayField(row, indexPattern.timeFieldName),
filterable: mapping(indexPattern.timeFieldName).filterable && $scope.filter,
column: indexPattern.timeFieldName,
})
);
}
$scope.columns.forEach(function (column: any) {
const isFilterable = mapping(column) && mapping(column).filterable && $scope.filter;
// Written By: Satyam Shukla
// Logic For: Showing Country Flag Beside Country Name, If Country Code Exists
let formattedValue = _displayField(row, column, true);
let flagClassName = '';
// Check if the column is 'network.dst_geo_ip.country_name'
if (column === 'network.dst_geo_ip.country_name' && row._source && row._source.network && row._source.network.dst_geo_ip) {
const countryCode = row._source.network.dst_geo_ip.country_code;
// Determine the CSS class name for the flag based on the country code
if (countryCode && isValidCountryCode(countryCode.toUpperCase())) {
flagClassName = `flag flag-${countryCode.toLowerCase()}`;
}
}
// Check if the column is 'network.src_geo_ip.country_name'
if (column === 'network.src_geo_ip.country_name' && row._source && row._source.network && row._source.network.src_geo_ip) {
const countryCode = row._source.network.src_geo_ip.country_code;
// Determine the CSS class name for the flag based on the country code
if (countryCode && isValidCountryCode(countryCode.toUpperCase())) {
flagClassName = `flag flag-${countryCode.toLowerCase()}`;
// console.log("SRC Country Code: ", countryCode, "Flag Class: ", flagClassName)
}
}
// Prepend the flag HTML to the formatted value
if (flagClassName) {
formattedValue = `<span class="${flagClassName}"></span> <span style="position: relative; top: 4px;">${formattedValue}</span>`;
}
newHtmls.push(
cellTemplate({
timefield: false,
sourcefield: column === '_source',
formatted: formattedValue,
filterable: isFilterable,
column,
})
);
});
let $cells = $el.children();
newHtmls.forEach(function (html, i) {
const $cell = $cells.eq(i);
if ($cell.data('discover:html') === html) return;
const reuse = find($cells.slice(i + 1), function (cell: any) {
return $.data(cell, 'discover:html') === html;
});
const $target = reuse ? $(reuse).detach() : $(html);
$target.data('discover:html', html);
const $before = $cells.eq(i - 1);
if ($before.length) {
$before.after($target);
} else {
$el.append($target);
}
// rebuild cells since we modified the children
$cells = $el.children();
if (!reuse) {
$toggleScope = $scope.$new();
$compile($target)($toggleScope);
}
});
if ($scope.open) {
$detailsScope.row = row;
}
// trim off cells that were not used rest of the cells
$cells.filter(':gt(' + (newHtmls.length - 1) + ')').remove();
dispatchRenderComplete($el[0]);
}
please help.