diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts index 1c41424ab..2429d2890 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts @@ -13,93 +13,100 @@ import { TableActionActivationService } from '../../table-actions-module/table-a @Injectable() export class ActivateActionsInEventUseCase - extends AbstractUseCase - implements IActivateTableActionsInRule + extends AbstractUseCase + implements IActivateTableActionsInRule { - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - private tableLogsService: TableLogsService, - private tableActionActivationService: TableActionActivationService, - ) { - super(); - } + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + private tableLogsService: TableLogsService, + private tableActionActivationService: TableActionActivationService, + ) { + super(); + } - public async implementation(inputData: ActivateEventActionsDS): Promise { - const { connection_data, request_body, event_id } = inputData; - const { connectionId, masterPwd, userId } = connection_data; - let operationResult = OperationResultStatusEnum.unknown; - const foundActionsWithCustomEvents = - await this._dbContext.tableActionRepository.findActionsWithCustomEventsByEventIdConnectionId( - event_id, - connectionId, - ); + public async implementation(inputData: ActivateEventActionsDS): Promise { + const { connection_data, request_body, event_id } = inputData; + const { connectionId, masterPwd, userId } = connection_data; + let operationResult = OperationResultStatusEnum.unknown; + const foundActionsWithCustomEvents = + await this._dbContext.tableActionRepository.findActionsWithCustomEventsByEventIdConnectionId( + event_id, + connectionId, + ); - if (!foundActionsWithCustomEvents.length) { - throw new HttpException( - { - message: Messages.NO_CUSTOM_ACTIONS_FOUND_FOR_THIS_RULE, - }, - HttpStatus.BAD_REQUEST, - ); - } - const tableName = foundActionsWithCustomEvents[0].action_rule.table_name; - const canUserReadTable = await this._dbContext.userAccessRepository.checkTableRead(userId, connectionId, tableName, masterPwd); - if (!canUserReadTable) { - throw new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS); - } + if (!foundActionsWithCustomEvents.length) { + throw new HttpException( + { + message: Messages.NO_CUSTOM_ACTIONS_FOUND_FOR_THIS_RULE, + }, + HttpStatus.BAD_REQUEST, + ); + } + const tableName = foundActionsWithCustomEvents[0].action_rule.table_name; + const canUserReadTable = await this._dbContext.userAccessRepository.checkTableRead( + userId, + connectionId, + tableName, + masterPwd, + ); + if (!canUserReadTable) { + throw new ForbiddenException(Messages.DONT_HAVE_PERMISSIONS); + } - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPwd, + ); - let locationFromResult: string = null; - const activationResults: Array<{ actionId: string; result: OperationResultStatusEnum }> = []; + let locationFromResult: string = null; + const activationResults: Array<{ actionId: string; result: OperationResultStatusEnum }> = []; - for (const action of foundActionsWithCustomEvents) { - let primaryKeyValuesArray: Array> = []; - try { - const { receivedOperationResult, receivedPrimaryKeysObj, location } = - await this.tableActionActivationService.activateTableAction( - action, - foundConnection, - request_body, - userId, - tableName, - null, - ); - operationResult = receivedOperationResult; - primaryKeyValuesArray = receivedPrimaryKeysObj; - if (location) { - locationFromResult = location; - } - activationResults.push({ actionId: action.id, result: operationResult }); - } catch (e) { - operationResult = OperationResultStatusEnum.unsuccessfully; - activationResults.push({ actionId: action.id, result: operationResult }); - throw new HttpException( - { - message: e.message, - }, - e.response?.status || HttpStatus.BAD_REQUEST, - ); - } finally { - for (const primaryKey of primaryKeyValuesArray) { - const logRecord = { - table_name: tableName, - userId: userId, - connection: foundConnection, - operationType: LogOperationTypeEnum.actionActivated, - operationStatusResult: operationResult, - row: primaryKey, - old_data: null, - table_primary_key: primaryKey, - }; - await this.tableLogsService.crateAndSaveNewLogUtil(logRecord); - } - } - } - return { location: locationFromResult, activationResults }; - } + for (const action of foundActionsWithCustomEvents) { + let primaryKeyValuesArray: Array> = []; + try { + const { receivedOperationResult, receivedPrimaryKeysObj, location } = + await this.tableActionActivationService.activateTableAction( + action, + foundConnection, + request_body, + userId, + tableName, + null, + ); + operationResult = receivedOperationResult; + primaryKeyValuesArray = receivedPrimaryKeysObj; + if (location) { + locationFromResult = location; + } + activationResults.push({ actionId: action.id, result: operationResult }); + } catch (e) { + operationResult = OperationResultStatusEnum.unsuccessfully; + activationResults.push({ actionId: action.id, result: operationResult }); + throw new HttpException( + { + message: e.message, + }, + e.response?.status || HttpStatus.BAD_REQUEST, + ); + } finally { + const eventTitle = action.action_rule.action_events?.find((e) => e.id === event_id)?.title ?? null; + for (const primaryKey of primaryKeyValuesArray) { + const logRecord = { + table_name: tableName, + userId: userId, + connection: foundConnection, + operationType: LogOperationTypeEnum.actionActivated, + operationStatusResult: operationResult, + row: primaryKey, + old_data: null, + table_primary_key: primaryKey, + operation_custom_action_name: eventTitle, + }; + await this.tableLogsService.crateAndSaveNewLogUtil(logRecord); + } + } + } + return { location: locationFromResult, activationResults }; + } } diff --git a/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts b/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts index 0a2553146..954271abb 100644 --- a/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts +++ b/backend/src/entities/table-logs/application/data-structures/create-log-record.ds.ts @@ -2,12 +2,13 @@ import { ConnectionEntity } from '../../../connection/connection.entity.js'; import { LogOperationTypeEnum, OperationResultStatusEnum } from '../../../../enums/index.js'; export class CreateLogRecordDs { - connection: ConnectionEntity; - old_data?: unknown; - operationStatusResult: OperationResultStatusEnum; - operationType: LogOperationTypeEnum; - row?: string | Record; - table_name: string; - userId: string; - affected_primary_key?: string | Record; + connection: ConnectionEntity; + old_data?: unknown; + operationStatusResult: OperationResultStatusEnum; + operationType: LogOperationTypeEnum; + row?: string | Record; + table_name: string; + userId: string; + affected_primary_key?: string | Record; + operation_custom_action_name?: string; } diff --git a/backend/src/entities/table-logs/application/data-structures/found-logs.ds.ts b/backend/src/entities/table-logs/application/data-structures/found-logs.ds.ts index ede9f80e7..36802bc49 100644 --- a/backend/src/entities/table-logs/application/data-structures/found-logs.ds.ts +++ b/backend/src/entities/table-logs/application/data-structures/found-logs.ds.ts @@ -33,6 +33,9 @@ export class FoundLogRecordDs { @ApiProperty() table_name: string; + + @ApiProperty() + operation_custom_action_name: string | null; } export class FoundLogsDs { diff --git a/backend/src/entities/table-logs/table-logs.entity.ts b/backend/src/entities/table-logs/table-logs.entity.ts index c46469e62..b7da56cd9 100644 --- a/backend/src/entities/table-logs/table-logs.entity.ts +++ b/backend/src/entities/table-logs/table-logs.entity.ts @@ -36,6 +36,9 @@ export class TableLogsEntity { @Column({ default: null }) email: string; + @Column({ default: null }) + operation_custom_action_name: string | null; + @Column('enum', { nullable: false, enum: LogOperationTypeEnum, diff --git a/backend/src/entities/table-logs/utils/build-found-log-record-ds.ts b/backend/src/entities/table-logs/utils/build-found-log-record-ds.ts index f93bf65dd..ab306bef5 100644 --- a/backend/src/entities/table-logs/utils/build-found-log-record-ds.ts +++ b/backend/src/entities/table-logs/utils/build-found-log-record-ds.ts @@ -25,5 +25,6 @@ export function buildFoundLogRecordDs(log: TableLogsEntity): FoundLogRecordDs { createdAt: createdAt, connection_id: connection_id.id, affected_primary_key: affected_primary_key, + operation_custom_action_name: log.operation_custom_action_name || null, }; } diff --git a/backend/src/entities/table-logs/utils/build-table-logs-entity.ts b/backend/src/entities/table-logs/utils/build-table-logs-entity.ts index 93214d77b..39002f9dd 100644 --- a/backend/src/entities/table-logs/utils/build-table-logs-entity.ts +++ b/backend/src/entities/table-logs/utils/build-table-logs-entity.ts @@ -2,18 +2,28 @@ import { CreateLogRecordDs } from '../application/data-structures/create-log-rec import { TableLogsEntity } from '../table-logs.entity.js'; export function buildTableLogsEntity(logData: CreateLogRecordDs, userEmail: string): TableLogsEntity { - const { connection, old_data, operationStatusResult, operationType, row, table_name, userId, affected_primary_key } = - logData; - const newLogs = new TableLogsEntity(); - newLogs.received_data = row as string; - newLogs.table_name = table_name; - newLogs.cognitoUserName = userId; - newLogs.email = userEmail.toLowerCase(); - newLogs.createdAt = new Date(); - newLogs.operationType = operationType; - newLogs.operationStatusResult = operationStatusResult; - newLogs.connection_id = connection; - newLogs.old_data = old_data as string; - newLogs.affected_primary_key = affected_primary_key as unknown as string; - return newLogs; + const { + connection, + old_data, + operationStatusResult, + operationType, + row, + table_name, + userId, + affected_primary_key, + operation_custom_action_name, + } = logData; + const newLogs = new TableLogsEntity(); + newLogs.received_data = row as string; + newLogs.table_name = table_name; + newLogs.cognitoUserName = userId; + newLogs.email = userEmail.toLowerCase(); + newLogs.createdAt = new Date(); + newLogs.operationType = operationType; + newLogs.operationStatusResult = operationStatusResult; + newLogs.connection_id = connection; + newLogs.old_data = old_data as string; + newLogs.affected_primary_key = affected_primary_key as unknown as string; + newLogs.operation_custom_action_name = operation_custom_action_name ?? null; + return newLogs; } diff --git a/backend/src/migrations/1770626855789-AddOperationCustomActionNameToTableLogsEntity.ts b/backend/src/migrations/1770626855789-AddOperationCustomActionNameToTableLogsEntity.ts new file mode 100644 index 000000000..a13cab861 --- /dev/null +++ b/backend/src/migrations/1770626855789-AddOperationCustomActionNameToTableLogsEntity.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOperationCustomActionNameToTableLogsEntity1770626855789 implements MigrationInterface { + name = 'AddOperationCustomActionNameToTableLogsEntity1770626855789'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableLogs" ADD "operation_custom_action_name" character varying`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tableLogs" DROP COLUMN "operation_custom_action_name"`); + } +} diff --git a/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts b/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts index 1bb81349c..4f7b7c612 100644 --- a/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts @@ -1328,3 +1328,91 @@ test.serial(`${currentTest} should create trigger and activate http table action t.truthy(userUpdatedRowSlackMessageRequestBody.text.includes(`[{"id":"1"}]`)); scope.done(); }); + +currentTest = 'POST /event/actions/activate/:eventId/:connectionId - logs operation_custom_action_name'; + +test.serial(`${currentTest} should log custom action event title in operation_custom_action_name field`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + await resetPostgresTestDB(); + + const fakeUrl = 'http://www.example.com'; + const customEventTitle = 'Test Custom Action Title'; + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule for logging', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: customEventTitle, + icon: 'test-icon', + require_confirmation: false, + }, + ], + table_actions: [ + { + url: fakeUrl, + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [], + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const scope = nock(fakeUrl) + .post('/') + .reply(201, () => { + return { + status: 201, + message: 'Table action was triggered', + }; + }); + + const activateTableRuleResult = await request(app.getHttpServer()) + .post(`/event/actions/activate/${createTableRuleRO.events[0].id}/${createConnectionRO.id}`) + .set('Cookie', token) + .send([{ id: 1 }]) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const activateTableRuleRO: ActivatedTableActionsDTO = JSON.parse(activateTableRuleResult.text); + t.is(activateTableRuleResult.status, 201); + t.is(Object.hasOwn(activateTableRuleRO, 'activationResults'), true); + + // Check that the custom action activation was logged with the correct event title + const getLogsResponse = await request(app.getHttpServer()) + .get(`/logs/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(getLogsResponse.status, 200); + const getLogsRO = JSON.parse(getLogsResponse.text); + t.is(Object.hasOwn(getLogsRO, 'logs'), true); + t.is(getLogsRO.logs.length > 0, true); + + const actionActivatedLog = getLogsRO.logs.find((log) => log.operationType === 'actionActivated'); + t.truthy(actionActivatedLog); + t.is(actionActivatedLog.operation_custom_action_name, customEventTitle); + + scope.done(); +});