From 22e0daea6ab889f9752cbdc3023f723409d3bda5 Mon Sep 17 00:00:00 2001 From: paulosf0 Date: Mon, 23 Sep 2024 09:59:12 -0300 Subject: [PATCH 01/17] fix: error when using advanced filters --- src/helpers/convert-gql.js | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/helpers/convert-gql.js b/src/helpers/convert-gql.js index cb6929205..9d95fe79f 100644 --- a/src/helpers/convert-gql.js +++ b/src/helpers/convert-gql.js @@ -16,7 +16,7 @@ const getGraphQLType = (value) => { return 'String' } - if (!isNaN(Date.parse(value))) { + if (isValidDate(value)) { return 'DateTime' } @@ -24,6 +24,29 @@ const getGraphQLType = (value) => { } } +function isValidDate(dateString) { + const datePatterns = [ + /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/, // YYYY-MM-DD + /^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/, // MM/DD/YYYY + /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/, // DD/MM/YYYY + /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/, // YYYY/MM/DD + /^(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])-\d{4}$/, // DD-MM-YYYY + /^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])-\d{4}$/, // MM-DD-YYYY + /^(0[1-9]|1[0-2])-(0[1-9]|[12]\d)-\d{2}$/, // MM-DD-YY + /^(0[1-9]|[12]\d)-(0[1-9]|1[0-2])-\d{2}$/, // DD-MM-YY + /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/ // YYYY-MM-DDTHH:mm:ss + ] + const matchesPattern = datePatterns.some((pattern) => pattern.test(dateString)) + + if (!matchesPattern) { + return false + } + + const date = new Date(dateString) + + return !isNaN(date.getTime()) +} + /** * Builds a GraphQL query based on the provided parameters. * @@ -138,9 +161,12 @@ const separateFieldsByType = (fields) => { const mergeFieldsIntoFilter = (fields, filter) => { fields.forEach(({ operator, valueField, value }) => { const filterKey = operator === 'In' ? 'in' : 'and' + const isOperetorContains = operator === 'Like' + + const fieldValue = isOperetorContains ? `%${value}%` : value filter[filterKey] = { ...filter[filterKey], - [`${valueField}${operator}`]: value + [`${valueField}${operator}`]: fieldValue } }) } From 43f3e4f3899aa0a59369cef4d84d7750342fd74d Mon Sep 17 00:00:00 2001 From: paulosf0 Date: Mon, 23 Sep 2024 11:07:32 -0300 Subject: [PATCH 02/17] refactor: rename isOperetorContainsto isOperatorContains --- src/helpers/convert-gql.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/convert-gql.js b/src/helpers/convert-gql.js index 9d95fe79f..c13b79142 100644 --- a/src/helpers/convert-gql.js +++ b/src/helpers/convert-gql.js @@ -161,9 +161,9 @@ const separateFieldsByType = (fields) => { const mergeFieldsIntoFilter = (fields, filter) => { fields.forEach(({ operator, valueField, value }) => { const filterKey = operator === 'In' ? 'in' : 'and' - const isOperetorContains = operator === 'Like' + const isOperatorContains = operator === 'Like' - const fieldValue = isOperetorContains ? `%${value}%` : value + const fieldValue = isOperatorContains ? `%${value}%` : value filter[filterKey] = { ...filter[filterKey], [`${valueField}${operator}`]: fieldValue From 7f8694ed2665fda70a73ef10908852538e8da51d Mon Sep 17 00:00:00 2001 From: Peterson Paulo <109550332+peterpaulo-azion@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:03:34 -0300 Subject: [PATCH 03/17] [UXE-4967] feat: added allow rules track events in waf tuning (#1727) --- .../analytics/AnalyticsTrackerAdapter.js | 18 ++++- .../analytics/trackers/ProductTracker.js | 13 ++++ .../analytics/trackers/WafRulesTracker.js | 69 +++++++++++++++++ src/plugins/analytics/trackers/index.js | 3 +- .../analytics/AnalyticsTrackerAdapter.test.js | 75 +++++++++++++++++++ src/views/WafRules/Dialog/index.vue | 1 + src/views/WafRules/ListWafRulesAllowed.vue | 5 ++ src/views/WafRules/ListWafRulesTuning.vue | 44 +++++++++-- 8 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 src/plugins/analytics/trackers/WafRulesTracker.js diff --git a/src/plugins/analytics/AnalyticsTrackerAdapter.js b/src/plugins/analytics/AnalyticsTrackerAdapter.js index b74292c96..68587e4c5 100644 --- a/src/plugins/analytics/AnalyticsTrackerAdapter.js +++ b/src/plugins/analytics/AnalyticsTrackerAdapter.js @@ -1,4 +1,10 @@ -import { SignUpTracker, SignInTracker, CreateTracker, ProductTracker } from './trackers' +import { + SignUpTracker, + SignInTracker, + CreateTracker, + ProductTracker, + WafRulesTracker +} from './trackers' /** * @typedef {Object} TrackerEvent @@ -27,6 +33,8 @@ export class AnalyticsTrackerAdapter { #createTracker = null /** @type {ProductTracker} */ #productTracker = null + /** @type {WafTracker} */ + #wafRulesTracker = null /** * Creates an instance of AnalyticsTrackerAdapter. @@ -40,6 +48,7 @@ export class AnalyticsTrackerAdapter { this.#signInTracker = new SignInTracker(this) this.#createTracker = new CreateTracker(this) this.#productTracker = new ProductTracker(this) + this.#wafRulesTracker = new WafRulesTracker(this) } /** @@ -119,4 +128,11 @@ export class AnalyticsTrackerAdapter { get product() { return this.#productTracker } + + /** + * @return {ProductTracker} + */ + get wafRules() { + return this.#wafRulesTracker + } } diff --git a/src/plugins/analytics/trackers/ProductTracker.js b/src/plugins/analytics/trackers/ProductTracker.js index e0557248b..df5940e70 100644 --- a/src/plugins/analytics/trackers/ProductTracker.js +++ b/src/plugins/analytics/trackers/ProductTracker.js @@ -139,4 +139,17 @@ export class ProductTracker { }) return this.#trackerAdapter } + + /** + * @param {Object} payload + * @param {String} payload.target + * @returns {AnalyticsTrackerAdapter} + */ + clickedOn(payload) { + this.#trackerAdapter.addEvent({ + eventName: `Clicked on ${payload.target}`, + props: {} + }) + return this.#trackerAdapter + } } diff --git a/src/plugins/analytics/trackers/WafRulesTracker.js b/src/plugins/analytics/trackers/WafRulesTracker.js new file mode 100644 index 000000000..cdf6ef6f8 --- /dev/null +++ b/src/plugins/analytics/trackers/WafRulesTracker.js @@ -0,0 +1,69 @@ +export class WafRulesTracker { + /** + * Interface for TrackerAdapter. + * @typedef {Object} trackerAdapter + * @property {function({eventName: string, props: Object}): void} addEvent - Method to add an event. + */ + #trackerAdapter + + /** + * @param {trackerAdapter} trackerAdapter + */ + constructor(adapter) { + this.#trackerAdapter = adapter + } + + /** + * @param {Object} payload + * @param {'drawer'|'page'} payload.origin + * + * @returns {AnalyticsTrackerAdapter} + */ + clickedToAllowRules(payload) { + this.#trackerAdapter.addEvent({ + eventName: 'Clicked to Allow Rules', + props: { + origin: payload.origin + } + }) + return this.#trackerAdapter + } + + /** + * @param {Object} payload + * @param {'drawer'|'page'} payload.origin + * + * @returns {AnalyticsTrackerAdapter} + */ + allowedRules(payload) { + this.#trackerAdapter.addEvent({ + eventName: 'Allowed Rules', + props: { + origin: payload.origin + } + }) + return this.#trackerAdapter + } + + /** + * @param {Object} payload + * @param {AzionProductsNames} payload.productName + * @param {String} payload.errorType + * @param {String} payload.fieldName + * @param {String} payload.errorMessage + * @param {'drawer'|'page'} payload.origin + * @returns {AnalyticsTrackerAdapter} + */ + failedToAllowRules(payload) { + this.#trackerAdapter.addEvent({ + eventName: 'Failed to Allow Rules', + props: { + errorType: payload.errorType, + fieldName: payload.fieldName, + errorMessage: payload.errorMessage, + origin: payload.origin + } + }) + return this.#trackerAdapter + } +} diff --git a/src/plugins/analytics/trackers/index.js b/src/plugins/analytics/trackers/index.js index 39fd5890e..326a99c82 100644 --- a/src/plugins/analytics/trackers/index.js +++ b/src/plugins/analytics/trackers/index.js @@ -2,5 +2,6 @@ import { SignUpTracker } from './SignUpTracker' import { SignInTracker } from './SignInTracker' import { CreateTracker } from './CreateTracker' import { ProductTracker } from './ProductTracker' +import { WafRulesTracker } from './WafRulesTracker' -export { SignUpTracker, SignInTracker, CreateTracker, ProductTracker } +export { SignUpTracker, SignInTracker, CreateTracker, ProductTracker, WafRulesTracker } diff --git a/src/tests/plugins/analytics/AnalyticsTrackerAdapter.test.js b/src/tests/plugins/analytics/AnalyticsTrackerAdapter.test.js index bb08d98f0..fee360258 100644 --- a/src/tests/plugins/analytics/AnalyticsTrackerAdapter.test.js +++ b/src/tests/plugins/analytics/AnalyticsTrackerAdapter.test.js @@ -515,4 +515,79 @@ describe('AnalyticsTrackerAdapter', () => { fieldName: fieldName }) }) + + it('should be able to track clicked on with correct params', () => { + const { sut, analyticsClientSpy } = makeSut() + const targetName = 'Search' + + sut.product.clickedOn({ + target: targetName + }) + + sut.track() + + expect(analyticsClientSpy.track).toHaveBeenCalledWith('Clicked on Search', { + application: fixtures.application + }) + }) + + // Waf Rules Tracker - Scenarios + it('should be able to track click to allow rules with correct params', () => { + const { sut, analyticsClientSpy } = makeSut() + const originName = 'page' + + sut.wafRules.clickedToAllowRules({ + origin: originName + }) + + sut.track() + + expect(analyticsClientSpy.track).toHaveBeenCalledWith('Clicked to Allow Rules', { + application: fixtures.application, + origin: 'page' + }) + }) + + it('should be able to track allow rules with correct params', () => { + const { sut, analyticsClientSpy } = makeSut() + const originName = 'page' + + sut.wafRules.allowedRules({ + origin: originName + }) + + sut.track() + + expect(analyticsClientSpy.track).toHaveBeenCalledWith('Allowed Rules', { + application: fixtures.application, + origin: originName + }) + }) + + it('should be able to track failed edited event with correct params', () => { + const { sut, analyticsClientSpy } = makeSut() + const productNameMock = 'Origin' + const errorMessageMock = 'message' + const errorTypeMock = 'API' + const fieldName = 'detail' + const originName = 'page' + + sut.wafRules.failedToAllowRules({ + productName: productNameMock, + errorMessage: errorMessageMock, + errorType: errorTypeMock, + fieldName: fieldName, + origin: 'page' + }) + + sut.track() + + expect(analyticsClientSpy.track).toHaveBeenCalledWith('Failed to Allow Rules', { + application: fixtures.application, + errorMessage: errorMessageMock, + errorType: errorTypeMock, + fieldName: fieldName, + origin: originName + }) + }) }) diff --git a/src/views/WafRules/Dialog/index.vue b/src/views/WafRules/Dialog/index.vue index 27f9a75bd..a77afa4eb 100644 --- a/src/views/WafRules/Dialog/index.vue +++ b/src/views/WafRules/Dialog/index.vue @@ -99,6 +99,7 @@ severity="secondary" label="Allow Rules" :loading="loading" + :disabled="!reason" @click="handleAllowRule" iconPos="right" /> diff --git a/src/views/WafRules/ListWafRulesAllowed.vue b/src/views/WafRules/ListWafRulesAllowed.vue index da425b65e..85704e776 100644 --- a/src/views/WafRules/ListWafRulesAllowed.vue +++ b/src/views/WafRules/ListWafRulesAllowed.vue @@ -309,6 +309,11 @@ } const openCreateDrawerWafAllowed = () => { + tracker.product + .clickToCreate({ + productName: 'Allowed Rule' + }) + .track() showCreateWafRulesAllowedDrawer.value = true handleCreateTrackEvent() } diff --git a/src/views/WafRules/ListWafRulesTuning.vue b/src/views/WafRules/ListWafRulesTuning.vue index 71e578324..7feb0a6e0 100644 --- a/src/views/WafRules/ListWafRulesTuning.vue +++ b/src/views/WafRules/ListWafRulesTuning.vue @@ -122,8 +122,12 @@ import advancedFilter from '@/templates/advanced-filter' import MultiSelect from 'primevue/multiselect' import { useToast } from 'primevue/usetoast' - import { computed, onMounted, ref } from 'vue' + import { computed, onMounted, ref, inject } from 'vue' import { useRoute, useRouter } from 'vue-router' + import { handleTrackerError } from '@/utils/errorHandlingTracker' + + /** @type {import('@/plugins/analytics/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */ + const tracker = inject('tracker') const props = defineProps({ listWafRulesTuningService: { @@ -180,7 +184,7 @@ const allowedByAttacks = ref([]) const selectedFilterAdvanced = ref([]) const listServiceWafTunningRef = ref('') - + const allowRuleOrigin = ref('') const valueDomains = computed({ get: () => { if (domainsOptions.value.done) return [] @@ -324,8 +328,10 @@ .map((domain) => domain.name) } - const openDialog = () => { + const openDialog = (origin = 'page') => { showDialogAllowRule.value = true + allowRuleOrigin.value = origin + tracker.wafRules.clickedToAllowRules({ origin }).track() } const cancelAllowed = () => { @@ -362,25 +368,46 @@ wafId: wafRuleId.value, description: reasonAttack }) + if (status === 'rejected') { - showToast(reason, 'error') - return + throw new Error(reason) } + showToast(value, 'success') filterSearch() closeDialog() selectedEvents.value = [] allowedByAttacks.value = [] showDetailsOfAttack.value = false + handleTrackAllowRule() } catch (error) { - showToast(error, 'error') + let errorMessage = error?.message || error + + handleTrackFailedToAllowRules(errorMessage) + showToast(errorMessage, 'error') } finally { isLoadingAllowed.value = false } } + const handleTrackFailedToAllowRules = (error) => { + const { fieldName, message } = handleTrackerError(error) + tracker.wafRules + .failedToAllowRules({ + errorType: 'api', + fieldName: fieldName.trim(), + errorMessage: message, + origin: allowRuleOrigin.value + }) + .track() + } + + const handleTrackAllowRule = () => { + tracker.wafRules.allowedRules({ origin: allowRuleOrigin.value }).track() + } + const createAllowedByAttack = (value) => { - openDialog() + openDialog('drawer') allowedByAttacks.value = value } @@ -390,6 +417,9 @@ const filterSearch = async (filter) => { if (!selectedFilter.value.domains.length) return + + tracker.product.clickedOn({ target: 'Search' }).track() + const { disabledIP, disabledCountries } = selectedFilter.value.network || {} listFields.value.find((item) => item.value === 'ip_address').disabled = disabledIP From 61294545c83053d7f530467a1086db6d99ad4f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariana=20Bellor=C3=ADn=20Aguilera?= <89138244+MarianaAguilera@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:47:51 -0300 Subject: [PATCH 04/17] [EDU-5386] chore: adjusting description bot graph (#1741) --- src/modules/real-time-metrics/constants/reports-texts.js | 6 ++++-- .../modules/real-time-metrics/constants/reports.test.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/modules/real-time-metrics/constants/reports-texts.js b/src/modules/real-time-metrics/constants/reports-texts.js index b3376e89c..deebd1cbc 100644 --- a/src/modules/real-time-metrics/constants/reports-texts.js +++ b/src/modules/real-time-metrics/constants/reports-texts.js @@ -189,7 +189,8 @@ const REPORTS_TEXTS = { description: 'Number of requests evaluated by Azion Bot Manager.' }, botTraffic: { - description: 'Sum of requests grouped by classification.' + description: + 'Sum of requests grouped by identifying traffic as Legitimate, Bad Bot, Good Bot, and Under Evaluation.' }, botCaptcha: { description: 'Sum of CAPTCHA challenge results returned for requests classified as bots.' @@ -199,7 +200,8 @@ const REPORTS_TEXTS = { 'Actions taken by Azion Bot Manager for requests identified as bots, displayed in both absolute values and percentages.' }, botClassification: { - description: 'Number of requests by bot categories.' + description: + 'Sum of requests classified according to the tactics used and the purpose of the bots.' }, botActivityMap: { description: 'Sum of requests identified as bots, presented by the country of origin.' diff --git a/src/tests/modules/real-time-metrics/constants/reports.test.js b/src/tests/modules/real-time-metrics/constants/reports.test.js index 5ccdc8344..088b29d6a 100644 --- a/src/tests/modules/real-time-metrics/constants/reports.test.js +++ b/src/tests/modules/real-time-metrics/constants/reports.test.js @@ -941,7 +941,8 @@ describe('RealTimeMetricsModule', () => { dashboardId: '371360344901061482', dataUnit: 'count', dataset: 'botManagerMetrics', - description: 'Sum of requests grouped by classification.', + description: + 'Sum of requests grouped by identifying traffic as Legitimate, Bad Bot, Good Bot, and Under Evaluation.', groupBy: ['classified'], helpCenterPath: '', id: '329891149133127508', @@ -1050,7 +1051,8 @@ describe('RealTimeMetricsModule', () => { id: '424388331488145485', chartOwner: 'azion', label: 'Bot Classifications', - description: 'Number of requests by bot categories.', + description: + 'Sum of requests classified according to the tactics used and the purpose of the bots.', aggregationType: 'sum', columns: 6, type: 'ordered-bar', From b6246832a46fa0acd2c429561d7c2e2c817f36b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariana=20Bellor=C3=ADn=20Aguilera?= <89138244+MarianaAguilera@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:54:35 -0300 Subject: [PATCH 05/17] [EDU-5387] chore: add links to help center in Bot Manager graphs (#1745) --- .../constants/help-center-urls.js | 16 ++++++++++++++- .../real-time-metrics/constants/reports.js | 20 +++++++++---------- .../constants/reports.test.js | 20 +++++++++---------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/modules/real-time-metrics/constants/help-center-urls.js b/src/modules/real-time-metrics/constants/help-center-urls.js index 0445d7537..1f39c65a9 100644 --- a/src/modules/real-time-metrics/constants/help-center-urls.js +++ b/src/modules/real-time-metrics/constants/help-center-urls.js @@ -76,7 +76,21 @@ const HELP_CENTER_URLS = { sqlInjectionThreats: '/real-time-metrics/waf/threats/sql-injection-threats', threatsVsRequests: '/real-time-metrics/waf/threats/threats-vs-requests' } - } + }, + botManager: { + botManagerSummary: { + badBotHits: '/real-time-metrics/bot-manager-advanced/overview/bad-bot-hits', + goodBotHits: '/real-time-metrics/bot-manager-advanced/overview/good-bot-hits', + botHits: '/real-time-metrics/bot-manager-advanced/overview/bot-hits', + transactions: '/real-time-metrics/bot-manager-advanced/overview/transactions', + botTraffic: '/real-time-metrics/bot-manager-advanced/overview/bot-traffic', + topBotAction: '/real-time-metrics/bot-manager-advanced/overview/top-bot-action', + botCaptchaLine: '/real-time-metrics/bot-manager-advanced/overview/bot-captcha-line-graph', + topBotCaptchaPie: '/real-time-metrics/bot-manager-advanced/overview/top-bot-captcha-pie-graph', + topBotClassification: '/real-time-metrics/bot-manager-advanced/overview/top-bot-classifications', + botActivityMap: '/real-time-metrics/bot-manager-advanced/overview/bot-activity-map' + } + }, } export default HELP_CENTER_URLS diff --git a/src/modules/real-time-metrics/constants/reports.js b/src/modules/real-time-metrics/constants/reports.js index 8a08edcf2..82a772f93 100644 --- a/src/modules/real-time-metrics/constants/reports.js +++ b/src/modules/real-time-metrics/constants/reports.js @@ -862,7 +862,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'inverse', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.badBotHits }, { id: '934654293238823255', @@ -892,7 +892,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'neutral', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.goodBotHits }, { id: '259047966206560862', @@ -922,7 +922,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'inverse', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.botHits }, { id: '541669034905662013', @@ -949,7 +949,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'neutral', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.transactions }, { id: '329891149133127508', @@ -975,7 +975,7 @@ const REPORTS = [ orderDirection: 'ASC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.botTraffic }, { id: '577704475532819772', @@ -1007,7 +1007,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.topBotAction }, { id: '071851224118431167', @@ -1036,7 +1036,7 @@ const REPORTS = [ orderDirection: 'ASC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.botCaptchaLine }, { id: '455330743572401794', @@ -1066,7 +1066,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.topBotCaptchaPie }, { id: '424388331488145485', @@ -1098,7 +1098,7 @@ const REPORTS = [ orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.topBotClassification }, { id: '190246009413028885', @@ -1128,7 +1128,7 @@ const REPORTS = [ orderDirection: 'ASC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: HELP_CENTER_URLS.botManager.botManagerSummary.botActivityMap }, /** * OBSERVE diff --git a/src/tests/modules/real-time-metrics/constants/reports.test.js b/src/tests/modules/real-time-metrics/constants/reports.test.js index 088b29d6a..c28cd1bdb 100644 --- a/src/tests/modules/real-time-metrics/constants/reports.test.js +++ b/src/tests/modules/real-time-metrics/constants/reports.test.js @@ -830,7 +830,7 @@ describe('RealTimeMetricsModule', () => { classifiedEq: 'bad bot' }, groupBy: [], - helpCenterPath: '', + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/bad-bot-hits', id: '892249168369791027', isTopX: false, label: 'Bad Bot Hits', @@ -860,7 +860,7 @@ describe('RealTimeMetricsModule', () => { classifiedEq: 'good bot' }, groupBy: [], - helpCenterPath: '', + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/good-bot-hits', id: '934654293238823255', isTopX: false, label: 'Good Bot Hits', @@ -890,7 +890,7 @@ describe('RealTimeMetricsModule', () => { classifiedIn: ['bad bot', 'good bot'] }, groupBy: [], - helpCenterPath: '', + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/bot-hits', id: '259047966206560862', isTopX: false, label: 'Bot Hits', @@ -917,7 +917,7 @@ describe('RealTimeMetricsModule', () => { description: 'Number of requests evaluated by Azion Bot Manager.', fields: [], groupBy: [], - helpCenterPath: '', + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/transactions', id: '541669034905662013', isTopX: false, label: 'Transactions', @@ -944,7 +944,7 @@ describe('RealTimeMetricsModule', () => { description: 'Sum of requests grouped by identifying traffic as Legitimate, Bad Bot, Good Bot, and Under Evaluation.', groupBy: ['classified'], - helpCenterPath: '', + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/bot-traffic', id: '329891149133127508', isTopX: false, label: 'Bot Traffic', @@ -986,7 +986,7 @@ describe('RealTimeMetricsModule', () => { orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/top-bot-action' }, { id: '071851224118431167', @@ -1015,7 +1015,7 @@ describe('RealTimeMetricsModule', () => { orderDirection: 'ASC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/bot-captcha-line-graph' }, { id: '455330743572401794', @@ -1045,7 +1045,7 @@ describe('RealTimeMetricsModule', () => { orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/top-bot-captcha-pie-graph' }, { id: '424388331488145485', @@ -1078,7 +1078,7 @@ describe('RealTimeMetricsModule', () => { orderDirection: 'DESC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/top-bot-classifications' }, { id: '190246009413028885', @@ -1108,7 +1108,7 @@ describe('RealTimeMetricsModule', () => { orderDirection: 'ASC', dashboardId: '371360344901061482', variationType: 'regular', - helpCenterPath: '' + helpCenterPath: '/real-time-metrics/bot-manager-advanced/overview/bot-activity-map' }, { aggregationType: 'sum', From 8b2b94df8ee084f114f2116ec185f425ea0368a0 Mon Sep 17 00:00:00 2001 From: paulosf0 Date: Tue, 24 Sep 2024 14:56:15 -0300 Subject: [PATCH 06/17] refactor: add test and refactor the code --- src/helpers/convert-gql.js | 44 +++++++++++---------- src/tests/helpers/convert-gql.test.js | 56 +++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/helpers/convert-gql.js b/src/helpers/convert-gql.js index c13b79142..28bae4f24 100644 --- a/src/helpers/convert-gql.js +++ b/src/helpers/convert-gql.js @@ -25,26 +25,22 @@ const getGraphQLType = (value) => { } function isValidDate(dateString) { - const datePatterns = [ - /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/, // YYYY-MM-DD - /^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/, // MM/DD/YYYY - /^(0[1-9]|[12]\d|3[01])\/(0[1-9]|1[0-2])\/\d{4}$/, // DD/MM/YYYY - /^\d{4}\/(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])$/, // YYYY/MM/DD - /^(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])-\d{4}$/, // DD-MM-YYYY - /^(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])-\d{4}$/, // MM-DD-YYYY - /^(0[1-9]|1[0-2])-(0[1-9]|[12]\d)-\d{2}$/, // MM-DD-YY - /^(0[1-9]|[12]\d)-(0[1-9]|1[0-2])-\d{2}$/, // DD-MM-YY - /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/ // YYYY-MM-DDTHH:mm:ss + const dateRegexes = [ + /^\d{2}\/\d{2}\/\d{4}$/, // dd/mm/yyyy + /^\d{4}\/\d{2}\/\d{2}$/, // yyyy/mm/dd + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/, // yyyy-mm-ddThh:mm:ss + /^\d{2}-\d{2}-\d{4}$/, // dd-mm-yyyy + /^\d{4}-\d{2}-\d{2}$/, // yyyy-mm-dd + /^\d{2}\/\d{1,2}\/\d{2}$/, // yy/m/d + /^\d{1,2}\/\d{1,2}\/\d{2}$/ // d/m/yy ] - const matchesPattern = datePatterns.some((pattern) => pattern.test(dateString)) - if (!matchesPattern) { + const isValidFormat = dateRegexes.some((regex) => regex.test(dateString)) + if (!isValidFormat) { return false } - const date = new Date(dateString) - - return !isNaN(date.getTime()) + return !isNaN(Date.parse(dateString)) } /** @@ -72,6 +68,15 @@ function buildGraphQLQuery({ filterParameter, dataset, limit, orderBy, filterQue ].join('\n') } +const formatValueContainOperator = (variable) => { + for (const key in variable) { + if (Object.prototype.hasOwnProperty.call(variable, key) && key.includes('Like')) { + variable[key] = `%${variable[key]}%` + } + } + return variable +} + /** * Convert filter and table to gql body * @@ -82,11 +87,11 @@ function buildGraphQLQuery({ filterParameter, dataset, limit, orderBy, filterQue const convertGQL = (filter, table) => { if (!table) throw new Error('Table parameter is required') - const variables = {} + let variables = {} const filterQuery = buildFilterQuery(filter, variables) - const fieldsFormat = table.fields.map((field) => `\t\t${field}`).join('\n') const filterParameter = formatFilterParameter(variables) + variables = formatValueContainOperator(variables) const queryConfig = { filterParameter, @@ -96,6 +101,7 @@ const convertGQL = (filter, table) => { filterQuery, fields: fieldsFormat } + const query = buildGraphQLQuery(queryConfig) return { @@ -161,12 +167,10 @@ const separateFieldsByType = (fields) => { const mergeFieldsIntoFilter = (fields, filter) => { fields.forEach(({ operator, valueField, value }) => { const filterKey = operator === 'In' ? 'in' : 'and' - const isOperatorContains = operator === 'Like' - const fieldValue = isOperatorContains ? `%${value}%` : value filter[filterKey] = { ...filter[filterKey], - [`${valueField}${operator}`]: fieldValue + [`${valueField}${operator}`]: value } }) } diff --git a/src/tests/helpers/convert-gql.test.js b/src/tests/helpers/convert-gql.test.js index da2062b5a..2d40ef12d 100644 --- a/src/tests/helpers/convert-gql.test.js +++ b/src/tests/helpers/convert-gql.test.js @@ -36,11 +36,11 @@ describe('convertGQL', () => { expect(sut(filter, fixtures.table)).toEqual({ query: expect.any(String), variables: { - tsRange_begin: '2022-01-01', - tsRange_end: '2022-01-31', and_field1: 'value1', and_field2: 'value2', - in_field3: ['value3', 'value4'] + in_field3: ['value3', 'value4'], + tsRange_begin: '2022-01-01', + tsRange_end: '2022-01-31' } }) }) @@ -141,4 +141,54 @@ describe('convertGQL', () => { } }) }) + + it('should return the correct type of variable in the query parameter', () => { + const filter = { + fields: [{ valueField: 'field2', operator: 'Like', value: '/2019' }] + } + + const { sut } = makeSut() + + const query = `query ( + $and_field2Like: String! +) { + myDataset ( + limit: 10 + orderBy: [field1] + filter: { + field2Like: $and_field2Like + } + ) { + field1 + field2 + field3 + } +}` + + expect(sut(filter, fixtures.table)).toEqual({ + query, + variables: { + and_field2Like: '%/2019%' + } + }) + }) + + it('Should format the value when the operator is contains', () => { + const filter = { + fields: [ + { valueField: 'field2', operator: 'Like', value: '/2019' }, + { valueField: 'field3', operator: 'Like', value: 'test' } + ] + } + + const { sut } = makeSut() + + expect(sut(filter, fixtures.table)).toEqual({ + query: expect.any(String), + variables: { + and_field2Like: '%/2019%', + and_field3Like: '%test%' + } + }) + }) }) From 62170a94da521c0cd08d1458e8dea35e96445d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariana=20Bellor=C3=ADn=20Aguilera?= <89138244+MarianaAguilera@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:14:24 -0300 Subject: [PATCH 07/17] [EDU-5386] chore: adjust Bot graphs names (#1747) --- src/modules/real-time-metrics/constants/reports.js | 6 +++--- .../modules/real-time-metrics/constants/reports.test.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/real-time-metrics/constants/reports.js b/src/modules/real-time-metrics/constants/reports.js index 82a772f93..57b3167c6 100644 --- a/src/modules/real-time-metrics/constants/reports.js +++ b/src/modules/real-time-metrics/constants/reports.js @@ -980,7 +980,7 @@ const REPORTS = [ { id: '577704475532819772', chartOwner: 'azion', - label: 'Bot Action', + label: 'Top Bot Action', description: REPORTS_TEXTS.botManager.botManagerSummary.botAction.description, aggregationType: 'sum', columns: 6, @@ -1041,7 +1041,7 @@ const REPORTS = [ { id: '455330743572401794', chartOwner: 'azion', - label: 'Bot CAPTCHA', + label: 'Top Bot CAPTCHA', description: REPORTS_TEXTS.botManager.botManagerSummary.botCaptcha.description, aggregationType: 'sum', columns: 6, @@ -1071,7 +1071,7 @@ const REPORTS = [ { id: '424388331488145485', chartOwner: 'azion', - label: 'Bot Classifications', + label: 'Top Bot Classifications', description: REPORTS_TEXTS.botManager.botManagerSummary.botClassification.description, aggregationType: 'sum', columns: 6, diff --git a/src/tests/modules/real-time-metrics/constants/reports.test.js b/src/tests/modules/real-time-metrics/constants/reports.test.js index c28cd1bdb..a876f3328 100644 --- a/src/tests/modules/real-time-metrics/constants/reports.test.js +++ b/src/tests/modules/real-time-metrics/constants/reports.test.js @@ -958,7 +958,7 @@ describe('RealTimeMetricsModule', () => { { id: '577704475532819772', chartOwner: 'azion', - label: 'Bot Action', + label: 'Top Bot Action', description: 'Actions taken by Azion Bot Manager for requests identified as bots, displayed in both absolute values and percentages.', aggregationType: 'sum', @@ -1020,7 +1020,7 @@ describe('RealTimeMetricsModule', () => { { id: '455330743572401794', chartOwner: 'azion', - label: 'Bot CAPTCHA', + label: 'Top Bot CAPTCHA', description: 'Sum of CAPTCHA challenge results returned for requests classified as bots.', aggregationType: 'sum', columns: 6, @@ -1050,7 +1050,7 @@ describe('RealTimeMetricsModule', () => { { id: '424388331488145485', chartOwner: 'azion', - label: 'Bot Classifications', + label: 'Top Bot Classifications', description: 'Sum of requests classified according to the tactics used and the purpose of the bots.', aggregationType: 'sum', From 2ae0f2f67772da5f433479493230293eb58c2f1a Mon Sep 17 00:00:00 2001 From: Aloisio Bastian <107002324+aloisio-m-bastian@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:38:06 -0300 Subject: [PATCH 08/17] feat: add edited and failed to edit your settings events to tracker (#1748) --- src/views/YourSettings/EditView.vue | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/views/YourSettings/EditView.vue b/src/views/YourSettings/EditView.vue index b643f692a..cc34a02c8 100644 --- a/src/views/YourSettings/EditView.vue +++ b/src/views/YourSettings/EditView.vue @@ -10,6 +10,7 @@ :loadService="loadUser" disableRedirect disableAfterCreateToastFeedback + @on-edit-fail="handleTrackFailEdit" @on-edit-success="successSubmit" > diff --git a/src/views/TeamsPermissions/EditView.vue b/src/views/TeamsPermissions/EditView.vue index cd63a39f8..2420dc3a5 100644 --- a/src/views/TeamsPermissions/EditView.vue +++ b/src/views/TeamsPermissions/EditView.vue @@ -13,6 +13,8 @@ :loadService="props.loadTeamPermissionService" :updatedRedirect="updatedRedirect" :schema="validationSchema" + @on-edit-success="handleTrackSuccessEdit" + @on-edit-fail="handleTrackFailEdit" > diff --git a/src/views/TeamsPermissions/ListView.vue b/src/views/TeamsPermissions/ListView.vue index da4a3c0f5..1bce39bb0 100644 --- a/src/views/TeamsPermissions/ListView.vue +++ b/src/views/TeamsPermissions/ListView.vue @@ -17,6 +17,8 @@ @on-load-data="handleLoadData" emptyListMessage="No teams found." :actions="actions" + @on-before-go-to-add-page="handleTrackEventGoToCreate" + @on-before-go-to-edit="handleTrackEventGoToEdit" > { + tracker.product.clickToCreate({ + productName: 'Teams Permissions' + }) + } + + const handleTrackEventGoToEdit = () => { + tracker.product.clickToEdit({ + productName: 'Teams Permissions' + }) + } From e428e4867727d938545869ebd9176f858ae3d8fa Mon Sep 17 00:00:00 2001 From: Herbert Vicente Cotta Julio Date: Wed, 25 Sep 2024 12:07:11 -0300 Subject: [PATCH 15/17] [UXE-5123] fix: remove unused API endpoint for allowed accounts (#1752) --- vite.config.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vite.config.js b/vite.config.js index b8c2812cc..e95f020f3 100644 --- a/vite.config.js +++ b/vite.config.js @@ -54,11 +54,6 @@ const getConfig = () => { changeOrigin: true, rewrite: (path) => path.replace(/^\/graphql\/billing/, '/billing/graphql') }, - '/api/allowed-accounts': { - target: `https://raw.githubusercontent.com/aziontech/console-client-list/main/clids.json`, - changeOrigin: true, - rewrite: (path) => path.replace(/^\/api\/allowed-accounts/, '') - }, '/api/webhook/console_feedback': { target: `https://automate.azion.net/`, changeOrigin: true, From b8601143422922288c24b7b0a5b49f29c1b35a08 Mon Sep 17 00:00:00 2001 From: Lucas Mendes <92529166+lucasmendes21@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:53:37 -0300 Subject: [PATCH 16/17] [UXE-4813] feat: persist number of records per page on list screens (#1750) * feat: persist number of records per page on list screens * chore: updated const name upper case to to lower case --- src/stores/table-definitions.js | 19 +++++++++++++++++++ src/templates/list-table-block/index.vue | 20 +++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 src/stores/table-definitions.js diff --git a/src/stores/table-definitions.js b/src/stores/table-definitions.js new file mode 100644 index 000000000..e653b165d --- /dev/null +++ b/src/stores/table-definitions.js @@ -0,0 +1,19 @@ +import { defineStore } from 'pinia' + +export const useTableDefinitionsStore = defineStore({ + id: 'tableDefinitions', + state: () => ({ + numberOfLinesPerPage: 10 + }), + persist: { + paths: ['numberOfLinesPerPage'] + }, + getters: { + getNumberOfLinesPerPage: (state) => state.numberOfLinesPerPage + }, + actions: { + setNumberOfLinesPerPage(numberOfLinesPerPage) { + this.numberOfLinesPerPage = numberOfLinesPerPage + } + } +}) diff --git a/src/templates/list-table-block/index.vue b/src/templates/list-table-block/index.vue index f92de9595..772c605f4 100644 --- a/src/templates/list-table-block/index.vue +++ b/src/templates/list-table-block/index.vue @@ -17,9 +17,10 @@ @row-click="editItemSelected" rowHover v-model:filters="filters" - :paginator="showPagination" + :paginator="true" :rowsPerPageOptions="[10, 20, 50, 100]" - :rows="MINIMUM_OF_ITEMS_PER_PAGE" + :rows="minimumOfItemsPerPage" + @page="changeNumberOfLinesPerPage" :globalFilterFields="filterBy" v-model:selection="selectedItems" :exportFilename="exportFileName" @@ -304,6 +305,7 @@ import { useToast } from 'primevue/usetoast' import { getCsvCellContentFromRowData } from '@/helpers' import { getArrayChangedIndexes } from '@/helpers/get-array-changed-indexes' + import { useTableDefinitionsStore } from '@/stores/table-definitions' defineOptions({ name: 'list-table-block-new' }) @@ -392,7 +394,9 @@ } }) - const MINIMUM_OF_ITEMS_PER_PAGE = 10 + const tableDefinitions = useTableDefinitionsStore() + + const minimumOfItemsPerPage = ref(tableDefinitions.getNumberOfLinesPerPage) const isRenderActions = !!props.actions?.length const isRenderOneOption = props.actions?.length === 1 const selectedId = ref(null) @@ -586,6 +590,12 @@ } } + const changeNumberOfLinesPerPage = (event) => { + const numberOfLinesPerPage = event.rows + tableDefinitions.setNumberOfLinesPerPage(numberOfLinesPerPage) + minimumOfItemsPerPage.value = numberOfLinesPerPage + } + const filterBy = computed(() => { const filtersPath = props.columns.filter((el) => el.filterPath).map((el) => el.filterPath) const filters = props.columns.map((item) => item.field) @@ -593,10 +603,6 @@ return [...filters, ...filtersPath] }) - const showPagination = computed(() => { - return data.value.length > MINIMUM_OF_ITEMS_PER_PAGE - }) - watch(data, (currentState) => { const hasData = currentState?.length > 0 emit('on-load-data', !!hasData) From 7a73928c8f3040891ef01624491caedc2e14878b Mon Sep 17 00:00:00 2001 From: Herbert Vicente Cotta Julio Date: Wed, 25 Sep 2024 14:47:31 -0300 Subject: [PATCH 17/17] chore: update project version to 1.22.3 (#1754) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d432cd291..6affa2eb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azion-console-kit", - "version": "1.22.2", + "version": "1.22.3", "private": false, "type": "module", "repository": {