Skip to content

Commit

Permalink
Generate case ids
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Oct 20, 2023
1 parent f184a08 commit c997e7e
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import { CasesConnector } from './cases_connector';
import { CASES_CONNECTOR_ID } from './constants';
import { CASE_ORACLE_SAVED_OBJECT } from '../../../common/constants';
import { CasesOracleService } from './cases_oracle_service';
import { CasesService } from './cases_service';

jest.mock('./cases_oracle_service');
jest.mock('./cases_service');

const CasesOracleServiceMock = CasesOracleService as jest.Mock;
const CasesServiceMock = CasesService as jest.Mock;

describe('CasesConnector', () => {
const services = actionsMock.createServices();
Expand Down Expand Up @@ -81,26 +84,39 @@ describe('CasesConnector', () => {
];

const mockGetRecordId = jest.fn();
const bulkGetRecords = jest.fn();
const bulkCreateRecord = jest.fn();
const mockBulkGetRecords = jest.fn();
const mockBulkCreateRecord = jest.fn();
const mockGetCaseId = jest.fn();

let connector: CasesConnector;

beforeEach(() => {
jest.clearAllMocks();

CasesOracleServiceMock.mockImplementation(() => {
let idCounter = 0;
let oracleIdCounter = 0;

return {
getRecordId: mockGetRecordId.mockImplementation(() => `so-oracle-record-${idCounter++}`),
bulkGetRecords: bulkGetRecords.mockResolvedValue(oracleRecords),
bulkCreateRecord: bulkCreateRecord.mockResolvedValue({
...oracleRecords[0],
id: groupedAlertsWithOracleKey[2].oracleKey,
grouping: groupedAlertsWithOracleKey[2].grouping,
version: 'so-version-2',
}),
getRecordId: mockGetRecordId.mockImplementation(
() => `so-oracle-record-${oracleIdCounter++}`
),
bulkGetRecords: mockBulkGetRecords.mockResolvedValue(oracleRecords),
bulkCreateRecord: mockBulkCreateRecord.mockResolvedValue([
{
...oracleRecords[0],
id: groupedAlertsWithOracleKey[2].oracleKey,
grouping: groupedAlertsWithOracleKey[2].grouping,
version: 'so-version-2',
},
]),
};
});

CasesServiceMock.mockImplementation(() => {
let caseIdCounter = 0;

return {
getCaseId: mockGetCaseId.mockImplementation(() => `so-case-id-${caseIdCounter++}`),
};
});

Expand All @@ -119,6 +135,8 @@ describe('CasesConnector', () => {
it('generates the oracle keys correctly', async () => {
await connector.run({ alerts, groupingBy, owner, rule });

expect(mockGetRecordId).toHaveBeenCalledTimes(3);

for (const [index, { grouping }] of groupedAlertsWithOracleKey.entries()) {
expect(mockGetRecordId).nthCalledWith(index + 1, {
ruleId: rule.id,
Expand All @@ -132,7 +150,7 @@ describe('CasesConnector', () => {
it('gets the oracle records correctly', async () => {
await connector.run({ alerts, groupingBy, owner, rule });

expect(bulkGetRecords).toHaveBeenCalledWith([
expect(mockBulkGetRecords).toHaveBeenCalledWith([
groupedAlertsWithOracleKey[0].oracleKey,
groupedAlertsWithOracleKey[1].oracleKey,
groupedAlertsWithOracleKey[2].oracleKey,
Expand All @@ -142,7 +160,7 @@ describe('CasesConnector', () => {
it('created the no found oracle records correctly', async () => {
await connector.run({ alerts, groupingBy, owner, rule });

expect(bulkCreateRecord).toHaveBeenCalledWith([
expect(mockBulkCreateRecord).toHaveBeenCalledWith([
{
recordId: groupedAlertsWithOracleKey[2].oracleKey,
payload: {
Expand All @@ -155,11 +173,29 @@ describe('CasesConnector', () => {
});

it('does not create oracle records if there are no 404 errors', async () => {
bulkGetRecords.mockResolvedValue([oracleRecords[0]]);
mockBulkGetRecords.mockResolvedValue([oracleRecords[0]]);

await connector.run({ alerts, groupingBy, owner, rule });

expect(bulkCreateRecord).not.toHaveBeenCalled();
expect(mockBulkCreateRecord).not.toHaveBeenCalled();
});
});

describe('Cases', () => {
it('generates the case ids correctly', async () => {
await connector.run({ alerts, groupingBy, owner, rule });

expect(mockGetCaseId).toHaveBeenCalledTimes(3);

for (const [index, { grouping }] of groupedAlertsWithOracleKey.entries()) {
expect(mockGetCaseId).nthCalledWith(index + 1, {
ruleId: rule.id,
grouping,
owner,
spaceId: 'default',
counter: 1,
});
}
});
});
});
Expand Down
80 changes: 63 additions & 17 deletions x-pack/plugins/cases/server/connectors/cases/cases_connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@ import type {
import { CasesConnectorRunParamsSchema } from './schema';
import { CasesOracleService } from './cases_oracle_service';
import { partitionRecords } from './utils';
import { CasesService } from './cases_service';

interface GroupedAlerts {
alerts: CasesConnectorRunParams['alerts'];
grouping: Record<string, unknown>;
}

type GroupedAlertsWithOracleKey = GroupedAlerts & { oracleKey: string };
type GroupedAlertsWithCaseId = GroupedAlertsWithOracleKey & { caseId: string };

export class CasesConnector extends SubActionConnector<
CasesConnectorConfig,
CasesConnectorSecrets
> {
private readonly casesOracleService;
private readonly casesOracleService: CasesOracleService;
private readonly casesService: CasesService;

constructor(params: ServiceParams<CasesConnectorConfig, CasesConnectorSecrets>) {
super(params);

this.casesOracleService = new CasesOracleService({
log: this.logger,
/**
Expand All @@ -46,6 +50,9 @@ export class CasesConnector extends SubActionConnector<
*/
unsecuredSavedObjectsClient: this.savedObjectsClient,
});

this.casesService = new CasesService();

this.registerSubActions();
}

Expand All @@ -66,6 +73,30 @@ export class CasesConnector extends SubActionConnector<
throw new Error('Method not implemented.');
}

public async run(params: CasesConnectorRunParams) {
const { alerts, groupingBy } = params;

/**
* TODO: Handle when grouping is not defined
* One case should be created per rule
*/
const groupedAlerts = this.groupAlerts({ alerts, groupingBy });
const groupedAlertsWithOracleKey = this.generateOracleKeys(params, groupedAlerts);

/**
* Add circuit breakers to the number of oracles they can be created or retrieved
*/
const oracleRecords = await this.bulkGetOrCreateOracleRecords(
Array.from(groupedAlertsWithOracleKey.values())
);

const groupedAlertsWithCaseId = this.generateCaseIds(
params,
groupedAlertsWithOracleKey,
oracleRecords
);
}

private groupAlerts({
alerts,
groupingBy,
Expand Down Expand Up @@ -94,7 +125,7 @@ export class CasesConnector extends SubActionConnector<
private generateOracleKeys(
params: CasesConnectorRunParams,
groupedAlerts: GroupedAlerts[]
): GroupedAlertsWithOracleKey[] {
): Map<string, GroupedAlertsWithOracleKey> {
const { rule, owner } = params;
/**
* TODO: Take spaceId from the actions framework
Expand All @@ -114,7 +145,7 @@ export class CasesConnector extends SubActionConnector<
oracleMap.set(oracleKey, { oracleKey, grouping, alerts });
}

return Array.from(oracleMap.values());
return oracleMap;
}

private async bulkGetOrCreateOracleRecords(
Expand Down Expand Up @@ -162,22 +193,37 @@ export class CasesConnector extends SubActionConnector<
return [...bulkGetValidRecords, ...bulkCreateValidRecords];
}

public async run(params: CasesConnectorRunParams) {
const { alerts, groupingBy } = params;
private generateCaseIds(
params: CasesConnectorRunParams,
groupedAlertsWithOracleKey: Map<string, GroupedAlertsWithOracleKey>,
oracleRecords: OracleRecord[]
): Map<string, GroupedAlertsWithCaseId> {
const { rule, owner } = params;

/**
* TODO: Handle when grouping is not defined
* One case should be created per rule
*/
const groupedAlerts = this.groupAlerts({ alerts, groupingBy });
const groupedAlertsWithOracleKey = this.generateOracleKeys(params, groupedAlerts);
console.log(
'🚀 ~ file: cases_connector.ts:175 ~ run ~ groupedAlertsWithOracleKey:',
groupedAlertsWithOracleKey
);
/**
* Add circuit breakers to the number of oracles they can be created or retrieved
* TODO: Take spaceId from the actions framework
*/
const oracleRecords = this.bulkGetOrCreateOracleRecords(groupedAlertsWithOracleKey);
const spaceId = 'default';

const casesMap = new Map<string, GroupedAlertsWithCaseId>();

for (const oracleRecord of oracleRecords) {
const { alerts, grouping } = groupedAlertsWithOracleKey.get(oracleRecord.id) ?? {
alerts: [],
grouping: {},
};

const caseId = this.casesService.getCaseId({
ruleId: rule.id,
grouping,
owner,
spaceId,
counter: oracleRecord.counter,
});

casesMap.set(caseId, { caseId, alerts, grouping, oracleKey: oracleRecord.id });
}

return casesMap;
}
}
Loading

0 comments on commit c997e7e

Please sign in to comment.