diff --git a/services/audit-service/openapi.json b/services/audit-service/openapi.json index bf437688bd..f76a516454 100644 --- a/services/audit-service/openapi.json +++ b/services/audit-service/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "Audit Service", "version": "1.0.0", - "description": "Audit logging microservice", + "description": "Audit logging Microservice", "contact": { "name": "Sourcefuse" } @@ -544,6 +544,20 @@ }, "actedOn": { "type": "string" + }, + "actedOnList": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "actionGroupList": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + } } }, "additionalProperties": false, diff --git a/services/audit-service/openapi.md b/services/audit-service/openapi.md index 4c54ea84ad..4fd5db2f56 100644 --- a/services/audit-service/openapi.md +++ b/services/audit-service/openapi.md @@ -20,7 +20,7 @@ headingLevel: 2 > Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. -Audit logging microservice +Audit logging Microservice Base URLs: @@ -46,7 +46,13 @@ const inputBody = '{ }, "deleted": true, "entityId": "string", - "actedOn": "string" + "actedOn": "string", + "actedOnList": [ + "string" + ], + "actionGroupList": [ + "string" + ] }'; const headers = { 'Content-Type':'application/json', @@ -77,7 +83,13 @@ const inputBody = { }, "deleted": true, "entityId": "string", - "actedOn": "string" + "actedOn": "string", + "actedOnList": [ + "string" + ], + "actionGroupList": [ + "string" + ] }; const headers = { 'Content-Type':'application/json', @@ -116,7 +128,13 @@ fetch('/audit-logs/archive', }, "deleted": true, "entityId": "string", - "actedOn": "string" + "actedOn": "string", + "actedOnList": [ + "string" + ], + "actionGroupList": [ + "string" + ] } ``` @@ -953,7 +971,13 @@ AuditLogWithRelations }, "deleted": true, "entityId": "string", - "actedOn": "string" + "actedOn": "string", + "actedOnList": [ + "string" + ], + "actionGroupList": [ + "string" + ] } ``` @@ -970,6 +994,8 @@ CustomFilter |deleted|boolean|false|none|none| |entityId|string|false|none|none| |actedOn|string|false|none|none| +|actedOnList|[string]|false|none|none| +|actionGroupList|[string]|false|none|none|

loopback.Count

diff --git a/services/audit-service/src/__tests__/integration/archive-log.controller.unit.ts b/services/audit-service/src/__tests__/integration/archive-log.controller.unit.ts index 22eb2176ab..9b3f707b35 100644 --- a/services/audit-service/src/__tests__/integration/archive-log.controller.unit.ts +++ b/services/audit-service/src/__tests__/integration/archive-log.controller.unit.ts @@ -41,7 +41,7 @@ describe('POST /audit-logs/archive', () => { afterEach(async () => { await app.stop(); }); - it('archive logs when all 3 parameters are provided and deleted is false', async () => { + it('archive logs when 3 parameters are provided and deleted is false', async () => { const customFilter: CustomFilter = new CustomFilter({ date: { fromDate: testFromDate, @@ -67,7 +67,7 @@ describe('POST /audit-logs/archive', () => { actualResult.length + controllerResult.numberOfEntriesArchived, ).to.be.equal(archiveLogs.length); }); - it('archive logs when all 3 parameters are provided and deleted is true', async () => { + it('archive logs when 3 parameters are provided and deleted is true', async () => { const customFilter: CustomFilter = new CustomFilter({ date: { fromDate: testFromDate, @@ -176,7 +176,7 @@ describe('POST /audit-logs/archive', () => { actualResult.length + controllerResult.numberOfEntriesArchived, ).to.be.equal(archiveLogs.length); }); - it('archive logs when only actedOn parameter is provided', async () => { + it('archive logs when only date parameter is provided', async () => { const customFilter: CustomFilter = new CustomFilter({ date: { fromDate: testFromDate, @@ -204,6 +204,58 @@ describe('POST /audit-logs/archive', () => { const mappingLogFetch = mappingLogRepositoryStub.stubs.create; mappingLogFetch.resolves(mappingLog); + const controllerResult = await auditLogController.archive(customFilter); + const actualResult = await auditLogRepository.find(); + expect( + actualResult.length + controllerResult.numberOfEntriesArchived, + ).to.be.equal(archiveLogs.length); + }); + it('archive logs when actedOnList parameter is provided', async () => { + const customFilter: CustomFilter = new CustomFilter({ + actedOnList: ['Product'], + }); + const {auditLogController} = getTestAuditController(app); + const mappingLogRepositoryStub = createStubInstance(MappingLogRepository); + const mappingLogFetch = mappingLogRepositoryStub.stubs.create; + mappingLogFetch.resolves(mappingLog); + + const controllerResult = await auditLogController.archive(customFilter); + const actualResult = await auditLogRepository.find(); + const expectedIds = ['6']; + + expect(actualResult).to.be.containDeep(expectedIds.map(id => ({id}))); + expect( + actualResult.length + controllerResult.numberOfEntriesArchived, + ).to.be.equal(archiveLogs.length); + }); + it('archive logs when actedOnList parameter is provided and deleted is true', async () => { + const customFilter: CustomFilter = new CustomFilter({ + actedOnList: ['Product'], + deleted: true, + }); + const {auditLogController} = getTestAuditController(app); + const mappingLogRepositoryStub = createStubInstance(MappingLogRepository); + const mappingLogFetch = mappingLogRepositoryStub.stubs.create; + mappingLogFetch.resolves(mappingLog); + + const controllerResult = await auditLogController.archive(customFilter); + const actualResult = await auditLogRepository.find(); + const expectedIds = ['3', '6']; + + expect(actualResult).to.be.containDeep(expectedIds.map(id => ({id}))); + expect( + actualResult.length + controllerResult.numberOfEntriesArchived, + ).to.be.equal(archiveLogs.length); + }); + it('archive logs when actionGroupList parameter is provided', async () => { + const customFilter: CustomFilter = new CustomFilter({ + actionGroupList: ['Product_group'], + }); + const {auditLogController} = getTestAuditController(app); + const mappingLogRepositoryStub = createStubInstance(MappingLogRepository); + const mappingLogFetch = mappingLogRepositoryStub.stubs.create; + mappingLogFetch.resolves(mappingLog); + const controllerResult = await auditLogController.archive(customFilter); const actualResult = await auditLogRepository.find(); expect( diff --git a/services/audit-service/src/__tests__/sample-data/archive-log.ts b/services/audit-service/src/__tests__/sample-data/archive-log.ts index f9dc021788..b3c33ec853 100644 --- a/services/audit-service/src/__tests__/sample-data/archive-log.ts +++ b/services/audit-service/src/__tests__/sample-data/archive-log.ts @@ -131,6 +131,7 @@ export const archive1: AuditLog[] = [ qty: 0, deleted: false, } as JSONObject, + actionGroup: 'Product_group', }), new AuditLog({ id: '12', @@ -155,6 +156,7 @@ export const archive1: AuditLog[] = [ qty: 0, deleted: false, } as JSONObject, + actionGroup: 'Product_group', }), new AuditLog({ id: '13', @@ -172,6 +174,7 @@ export const archive1: AuditLog[] = [ qty: 0, deleted: false, } as JSONObject, + actionGroup: 'Product_group', }), ]; export const archive2: AuditLog[] = [ diff --git a/services/audit-service/src/models/custom-filter.model.ts b/services/audit-service/src/models/custom-filter.model.ts index aa5dec9a1e..35b78cebf1 100644 --- a/services/audit-service/src/models/custom-filter.model.ts +++ b/services/audit-service/src/models/custom-filter.model.ts @@ -39,6 +39,31 @@ export class CustomFilter extends CoreModel { type: 'string', }) actedOn?: string; + + /** Both actedOnList and actionGroupList parameters accepts a + * list of values that you want to archive */ + + @property({ + jsonSchema: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, + }) + actedOnList?: string[]; //to avoid breaking change + + @property({ + jsonSchema: { + type: 'array', + uniqueItems: true, + items: { + type: 'string', + }, + }, + }) + actionGroupList?: string[]; } export type CustomFilterWithRelations = CustomFilter; diff --git a/services/audit-service/src/services/job-processing.service.ts b/services/audit-service/src/services/job-processing.service.ts index 907144e556..f65918fc68 100644 --- a/services/audit-service/src/services/job-processing.service.ts +++ b/services/audit-service/src/services/job-processing.service.ts @@ -1,5 +1,10 @@ import {BindingScope, inject, injectable} from '@loopback/core'; -import {EntityCrudRepository, Filter, repository} from '@loopback/repository'; +import { + AnyObject, + EntityCrudRepository, + Filter, + repository, +} from '@loopback/repository'; import {HttpErrors} from '@loopback/rest'; import {FileStatusKey} from '../enums/file-status-key.enum'; import {OperationKey} from '../enums/operation-key.enum'; @@ -24,6 +29,12 @@ import { ColumnBuilderFn, QuerySelectedFilesFn, } from '../types'; +import { + checkActedOn, + checkActionGroup, + checkDates, + checkEntityId, +} from '../utils/file-check'; @injectable({scope: BindingScope.TRANSIENT}) export class JobProcessingService { constructor( @@ -51,21 +62,7 @@ export class JobProcessingService { const customFilter = new CustomFilter(); if (filter?.where && 'and' in filter.where) { const andArray = filter.where?.and; - for (const condition of andArray) { - if (condition.actedAt?.between) { - const [start, end] = condition.actedAt.between; - customFilter.date = { - fromDate: start, - toDate: end, - }; - } - if (condition.actedOn) { - customFilter.actedOn = condition.actedOn; - } - if (condition.entityId) { - customFilter.entityId = condition.entityId; - } - } + this.buildCustomFilter(andArray, customFilter); } const mappingLogs: MappingLog[] = await this.mappingLogRepository.find(); const finalData: AuditLog[] = []; @@ -73,16 +70,10 @@ export class JobProcessingService { for (const mappingLog of mappingLogs) { const filterUsed: CustomFilter = mappingLog.filterUsed as CustomFilter; if ( - (customFilter.actedOn == null || - filterUsed.actedOn == null || - filterUsed.actedOn === customFilter.actedOn) && - (customFilter.entityId ?? - filterUsed.entityId ?? - filterUsed.entityId === customFilter.entityId) && - (customFilter.date == null || - filterUsed.date == null || - (filterUsed.date?.fromDate <= customFilter.date?.toDate && - filterUsed.date?.toDate >= customFilter.date?.fromDate)) + checkActedOn(filterUsed, customFilter) && + checkActionGroup(filterUsed, customFilter) && + checkEntityId(filterUsed, customFilter) && + checkDates(filterUsed, customFilter) ) { //logs from s3 finalData.push( @@ -106,4 +97,35 @@ export class JobProcessingService { throw new HttpErrors.UnprocessableEntity(error.message); } } + getFilter(inquiredFilter: string | AnyObject): string[] { + if (typeof inquiredFilter === 'string') { + return [inquiredFilter]; + } else return inquiredFilter.inq; + } + + haveCommonElements(arr1: string[], arr2: string[]): boolean { + return arr1.some(item => arr2.includes(item)); + } + + buildCustomFilter(andArray: AnyObject[], customFilter: CustomFilter) { + for (const condition of andArray) { + if (condition.actedAt?.between) { + const [start, end] = condition.actedAt.between; + customFilter.date = { + fromDate: start, + toDate: end, + }; + } + if (condition.actedOn) { + //even if actedOn is a string, it is converted to an array for easy comparision + customFilter.actedOnList = this.getFilter(condition.actedOn); + } + if (condition.actionGroup) { + customFilter.actionGroupList = this.getFilter(condition.actionGroup); + } + if (condition.entityId) { + customFilter.entityId = condition.entityId; + } + } + } } diff --git a/services/audit-service/src/utils/construct-where.ts b/services/audit-service/src/utils/construct-where.ts index 80bfdfad77..887451dafd 100644 --- a/services/audit-service/src/utils/construct-where.ts +++ b/services/audit-service/src/utils/construct-where.ts @@ -43,9 +43,19 @@ export async function constructWhere(customFilter: CustomFilter) { }); } - if (customFilter.actedOn) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (customFilter.actedOn || customFilter.actedOnList) { + const array = customFilter.actedOn + ? [customFilter.actedOn] + : customFilter.actedOnList; where.and.push({ - actedOn: customFilter.actedOn, + actedOn: {inq: array}, + }); + } + + if (customFilter.actionGroupList) { + where.and.push({ + actionGroup: {inq: customFilter.actionGroupList}, }); } return where; diff --git a/services/audit-service/src/utils/file-check.ts b/services/audit-service/src/utils/file-check.ts new file mode 100644 index 0000000000..d49f3b73a5 --- /dev/null +++ b/services/audit-service/src/utils/file-check.ts @@ -0,0 +1,63 @@ +import {CustomFilter} from '../models'; + +export function checkActedOn( + filterUsed: CustomFilter, + customFilter: CustomFilter, +): boolean | undefined { + // eslint-disable-next-line prefer-const + let {actedOn, actedOnList} = filterUsed; + const customActedOnList = customFilter.actedOnList; + actedOnList = actedOnList ?? []; + // Check if both actedOn and actedOnList are null or if customFilter's actedOnList is null + // Check if filterUsed.actedOn is defined and included in customFilter.actedOnList + // Check if both actedOnLists have common elements + if (!!actedOn) { + actedOnList = [...new Set([...actedOnList, actedOn])]; + } + + return ( + actedOnList.length === 0 || + !customActedOnList || + haveCommonElements(customActedOnList, actedOnList) + ); +} + +export function checkActionGroup( + filterUsed: CustomFilter, + customFilter: CustomFilter, +): boolean { + const actionGroupList = filterUsed.actionGroupList; + const customActionGroupList = customFilter.actionGroupList; + return ( + !customActionGroupList || + !actionGroupList || + haveCommonElements(actionGroupList, customActionGroupList) + ); +} + +export function checkEntityId( + filterUsed: CustomFilter, + customFilter: CustomFilter, +): boolean { + return ( + !customFilter.entityId || + !filterUsed.entityId || + filterUsed.entityId === customFilter.entityId + ); +} + +export function checkDates( + filterUsed: CustomFilter, + customFilter: CustomFilter, +) { + return ( + !customFilter.date || + !filterUsed.date || + (customFilter.date.toDate >= filterUsed.date.fromDate && + customFilter.date.fromDate <= filterUsed.date.toDate) + ); +} + +export function haveCommonElements(arr1: string[], arr2: string[]): boolean { + return arr1.some(item => arr2.includes(item)); +}