diff --git a/CHANGELOG.md b/CHANGELOG.md index 12db50f..d5f28aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Benchmarker - Changelog +## 6.1.0 + +- UI performance alerts are now suppressed if the same test suite and individual test have already triggered an alert within the last 3 days. + ## 6.0.2 - Update logic to generate UI Alerts to handle averages that are decimals. diff --git a/package-lock.json b/package-lock.json index cc522e7..7c439f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@apexdevtools/benchmarker", - "version": "6.0.2", + "version": "6.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@apexdevtools/benchmarker", - "version": "6.0.2", + "version": "6.1.0", "dependencies": { "@jsforce/jsforce-node": "3.2.0", "@salesforce/core": "7.3.6", diff --git a/package.json b/package.json index 956c8cf..b1d7a55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@apexdevtools/benchmarker", - "version": "6.0.2", + "version": "6.1.0", "description": "Benchmarks performance of processes on Salesforce Orgs", "author": { "name": "Apex Dev Tools Team", diff --git a/src/database/uiAlertInfo.ts b/src/database/uiAlertInfo.ts index fca28c5..f5bb2fa 100644 --- a/src/database/uiAlertInfo.ts +++ b/src/database/uiAlertInfo.ts @@ -128,3 +128,37 @@ export async function getAverageLimitValuesFromDB( return {}; } } + +export async function checkRecentUiAlerts( + suiteAndTestNamePairs: { testSuiteName: string; individualTestName: string }[] +) { + const connection = await getConnection(); + + const suiteAndTestNameConditions = suiteAndTestNamePairs + .map(pair => `('${pair.testSuiteName}', '${pair.individualTestName}')`) + .join(', '); + + const query = ` + SELECT test_suite_name, individual_test_name + FROM performance.ui_alert + WHERE create_date_time >= CURRENT_DATE - INTERVAL '3 days' + AND (test_suite_name, individual_test_name) IN (${suiteAndTestNameConditions}) + `; + + const existingAlerts = new Set(); + + try { + const result = await connection.query(query); + result.forEach( + (row: { test_suite_name: string; individual_test_name: string }) => { + existingAlerts.add( + `${row.test_suite_name}_${row.individual_test_name}` + ); + } + ); + } catch (error) { + console.error('Error checking recent UI alerts:', error); + } + + return existingAlerts; +} diff --git a/src/services/result/uiAlert.ts b/src/services/result/uiAlert.ts index 8b0d4f6..8c312fc 100644 --- a/src/services/result/uiAlert.ts +++ b/src/services/result/uiAlert.ts @@ -7,7 +7,10 @@ import { getNormalComponentLoadThreshold, getCriticalComponentLoadThreshold, } from '../../shared/env'; -import { getAverageLimitValuesFromDB } from '../../database/uiAlertInfo'; +import { + getAverageLimitValuesFromDB, + checkRecentUiAlerts, +} from '../../database/uiAlertInfo'; import { UiAlert } from '../../database/entity/uiAlert'; import { UiTestResultDTO } from '../../database/uiTestResult'; import { NORMAL, CRITICAL } from '../../shared/constants'; @@ -31,6 +34,17 @@ export async function generateValidAlerts( individualTestName: result.individualTestName, })); + const existingAlerts = await checkRecentUiAlerts(suiteAndTestNamePairs); + + const alertsToProcess = needToStoreAlert.filter( + item => + !existingAlerts.has(`${item.testSuiteName}_${item.individualTestName}`) + ); + + if (alertsToProcess.length === 0) { + return []; + } + // Fetch average values const preFetchedAverages = await getAverageLimitValuesFromDB( suiteAndTestNamePairs @@ -38,7 +52,7 @@ export async function generateValidAlerts( // Generate alerts const alerts = await Promise.all( - needToStoreAlert.map(async item => + alertsToProcess.map(async item => addAlertByComparingAvg(item, preFetchedAverages) ) ); diff --git a/test/database/uiAlertInfo.test.ts b/test/database/uiAlertInfo.test.ts index 61b62b9..5ac8833 100644 --- a/test/database/uiAlertInfo.test.ts +++ b/test/database/uiAlertInfo.test.ts @@ -5,6 +5,7 @@ import { getAverageLimitValuesFromDB, saveAlerts, + checkRecentUiAlerts, } from '../../src/database/uiAlertInfo'; import * as db from '../../src/database/connection'; import sinon from 'sinon'; @@ -329,4 +330,71 @@ describe('src/database/uiAlertInfo', () => { expect(savedRecords[0].uiTestResultId).to.equal(1); }); }); + + describe('checkRecentUiAlerts', () => { + it('should return a Set of keys for alerts found in the last 3 days', async () => { + // Given + const pairs = [ + { testSuiteName: 'SuiteA', individualTestName: 'Test1' }, + { testSuiteName: 'SuiteB', individualTestName: 'Test2' }, + ]; + + const mockDbRows = [ + { test_suite_name: 'SuiteA', individual_test_name: 'Test1' }, + ]; + + mockQuery.resolves(mockDbRows); + + // When + const result = await checkRecentUiAlerts(pairs); + + // Then + expect(mockQuery).to.have.been.calledOnce; + + const sqlQuery = mockQuery.firstCall.args[0]; + expect(sqlQuery).to.include("INTERVAL '3 days'"); + + expect(sqlQuery).to.include("('SuiteA', 'Test1')"); + expect(sqlQuery).to.include("('SuiteB', 'Test2')"); + + expect(result).to.be.instanceOf(Set); + expect(result.size).to.equal(1); + expect(result.has('SuiteA_Test1')).to.be.true; + expect(result.has('SuiteB_Test2')).to.be.false; + }); + + it('should return an empty Set if no recent alerts are found', async () => { + // Given + mockQuery.resolves([]); + + // When + const result = await checkRecentUiAlerts([ + { testSuiteName: 'SuiteA', individualTestName: 'Test1' }, + ]); + + // Then + expect(result).to.be.instanceOf(Set); + expect(result.size).to.equal(0); + }); + + it('should handle database errors gracefully by returning an empty Set', async () => { + // Given + const consoleStub = sinon.stub(console, 'error'); + mockQuery.rejects(new Error('Connection failed')); + + // When + const result = await checkRecentUiAlerts([ + { testSuiteName: 'SuiteA', individualTestName: 'Test1' }, + ]); + + // Then + expect(result).to.be.instanceOf(Set); + expect(result.size).to.equal(0); + expect(consoleStub).to.have.been.calledWith( + sinon.match('Error checking recent UI alerts') + ); + + consoleStub.restore(); + }); + }); }); diff --git a/test/services/uiAlert.test.ts b/test/services/uiAlert.test.ts index 9870a0a..c3df051 100644 --- a/test/services/uiAlert.test.ts +++ b/test/services/uiAlert.test.ts @@ -26,6 +26,7 @@ describe('generateValidAlerts', () => { let getAveragesStub: sinon.SinonStub; let normalThresholdStub: sinon.SinonStub; let criticalThresholdStub: sinon.SinonStub; + let checkRecentStub: sinon.SinonStub; before(() => { sandbox = sinon.createSandbox(); @@ -38,6 +39,7 @@ describe('generateValidAlerts', () => { ); getAveragesStub = sandbox.stub(uiAlertInfo, 'getAverageLimitValuesFromDB'); + checkRecentStub = sandbox.stub(uiAlertInfo, 'checkRecentUiAlerts'); }); beforeEach(() => { @@ -47,6 +49,7 @@ describe('generateValidAlerts', () => { normalThresholdStub.returns('20'); criticalThresholdStub.returns('50'); getAveragesStub.resolves({}); + checkRecentStub.resolves(new Set()); }); after(() => { @@ -100,6 +103,51 @@ describe('generateValidAlerts', () => { }); }); + describe('recent alert filtering', () => { + const avgFirst5 = 200; + it('should skip processing if an alert was already triggered for this test in the last 3 days', async () => { + // Given + const avgNext10 = 100; + const mockAverages = { + ['ComponentLoadSuite_ComponentXLoadTime']: { + avg_load_time_past_5_days: avgFirst5, + avg_load_time_6_to_15_days_ago: avgNext10, + }, + }; + checkRecentStub.resolves( + new Set(['ComponentLoadSuite_ComponentXLoadTime']) + ); + getAveragesStub.resolves(mockAverages); + + // When + const results = await generateValidAlerts([MOCK_TEST_DTO_BASE]); + + // Then + expect(results).to.be.an('array').that.is.empty; + expect(getAveragesStub).to.not.have.been.called; + }); + + it('should process the test normally if no recent alerts exist', async () => { + // Given + const avgNext10 = 100; + const mockAverages = { + ['ComponentLoadSuite_ComponentXLoadTime']: { + avg_load_time_past_5_days: avgFirst5, + avg_load_time_6_to_15_days_ago: avgNext10, + }, + }; + checkRecentStub.resolves(new Set()); + getAveragesStub.resolves(mockAverages); + + // When + const results = await generateValidAlerts([MOCK_TEST_DTO_BASE]); + + // Then + expect(results).to.have.lengthOf(1); + expect(getAveragesStub).to.have.been.called; + }); + }); + describe('alert generation logic', () => { const avgFirst5 = 200;