diff --git a/.github/workflows/cleanup_e2e_tests.yml b/.github/workflows/cleanup_e2e_tests.yml new file mode 100644 index 000000000..0c7b65fff --- /dev/null +++ b/.github/workflows/cleanup_e2e_tests.yml @@ -0,0 +1,27 @@ +name: Run cleanup e2e tests + +on: + schedule: + - cron: '0 8 * * *' + workflow_dispatch: + +jobs: + cleanup: + name: Cleanup e2e tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js Environment + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: yarn install + + - name: Cleanup Prod + env: + PROD_CYPRESS_TOKEN: ${{ secrets.PROD_CYPRESS_TOKEN }} + run: node scripts/cleanup.js PROD \ No newline at end of file diff --git a/.github/workflows/pre-merge.yml b/.github/workflows/pre-merge.yml index 4d74c491b..2b8672362 100644 --- a/.github/workflows/pre-merge.yml +++ b/.github/workflows/pre-merge.yml @@ -43,7 +43,7 @@ jobs: coverage/unit/sonar-report.xml retention-days: 1 - run-dev-e2e-tests: + run-prod-e2e-tests: name: Run Dev E2E Tests runs-on: ubuntu-latest strategy: @@ -61,15 +61,17 @@ jobs: - name: Run Cypress Tests uses: cypress-io/github-action@v6 env: - DEV_CYPRESS_EMAIL: ${{ secrets.DEV_CYPRESS_EMAIL }} - DEV_CYPRESS_PASSWORD: ${{ secrets.DEV_CYPRESS_PASSWORD }} + PROD_CYPRESS_EMAIL: ${{ secrets.PROD_CYPRESS_EMAIL }} + PROD_CYPRESS_PASSWORD: ${{ secrets.PROD_CYPRESS_PASSWORD }} + VITE_ENVIRONMENT: "production" with: build: yarn build start: yarn dev --logLevel=warn browser: chrome wait-on: 'http://localhost:5173/' wait-on-timeout: 120 - env: grepTags=@dev${{ matrix.group }}+-@xfail + config-file: cypress.config.prod.js + env: environment=prod,grepTags=@dev${{ matrix.group }}+-@xfail+-@dont_run_prod - name: Generate Specs JSON run: yarn generate-specs-json @@ -106,7 +108,7 @@ jobs: download_and_merge: name: Download and Merge Coverage Reports - needs: run-dev-e2e-tests + needs: run-prod-e2e-tests runs-on: ubuntu-latest steps: diff --git a/.github/workflows/prod-e2e-tests.yml b/.github/workflows/prod-e2e-tests.yml deleted file mode 100644 index 6f726d010..000000000 --- a/.github/workflows/prod-e2e-tests.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Prod E2E Tests - -on: - workflow_dispatch: - -jobs: - run-prod-e2e-tests: - name: Run Prod E2E Tests - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js Environment - uses: actions/setup-node@v4 - with: - node-version: 18 - - - name: Run Cypress E2E Tests - uses: cypress-io/github-action@v6 - env: - PROD_CYPRESS_EMAIL: ${{ secrets.PROD_CYPRESS_EMAIL }} - PROD_CYPRESS_PASSWORD: ${{ secrets.PROD_CYPRESS_PASSWORD }} - with: - browser: chrome - config-file: cypress.config.prod.js - env: environment=prod,grepTags=-@xfail - - - name: Upload Cypress Screenshots Artifact - if: ${{ failure() }} - uses: actions/upload-artifact@v3 - with: - name: cypress_screenshots - path: cypress/screenshots - retention-days: 4 - - - name: Upload Cypress Videos Artifact - if: ${{ failure() }} - uses: actions/upload-artifact@v3 - with: - name: cypress_videos - path: cypress/videos - retention-days: 1 diff --git a/cypress.config.prod.js b/cypress.config.prod.js index fdfb35ef8..d95f5265a 100644 --- a/cypress.config.prod.js +++ b/cypress.config.prod.js @@ -4,7 +4,6 @@ import baseConfig from './cypress.config'; const envOverride = { // TODO: remove this WORKAROUND for https://github.com/cypress-io/cypress/issues/20647, - baseUrl: 'http://console.azion.com', CYPRESS_EMAIL: process.env.PROD_CYPRESS_EMAIL, CYPRESS_PASSWORD: process.env.PROD_CYPRESS_PASSWORD, }; diff --git a/cypress.env.example.json b/cypress.env.example.json index 07bfd6c91..048687b97 100644 --- a/cypress.env.example.json +++ b/cypress.env.example.json @@ -1,10 +1,14 @@ { "DEV_CYPRESS_EMAIL": "", "DEV_CYPRESS_PASSWORD": "", + "DEV_CYPRESS_TOKEN": "", "STAGE_CYPRESS_EMAIL": "", "STAGE_CYPRESS_PASSWORD": "", + "STAGE_CYPRESS_TOKEN": "", "PREVIEW_PROD_CYPRESS_EMAIL": "", "PREVIEW_PROD_CYPRESS_PASSWORD": "", + "PREVIEW_PROD_CYPRESS_TOKEN": "", "PROD_CYPRESS_EMAIL": "", - "PROD_CYPRESS_PASSWORD": "" + "PROD_CYPRESS_PASSWORD": "", + "PROD_CYPRESS_TOKEN": "" } \ No newline at end of file diff --git a/cypress/README.md b/cypress/README.md index ead61a642..c5cb6adbd 100644 --- a/cypress/README.md +++ b/cypress/README.md @@ -116,10 +116,11 @@ For more details, see the official [@cypress/grep documentation](https://github. ### Tags Used in the Project -| Tag | Description | -|-------|-----------------------------------------------------| -| @dev | Runs the test in the DEV environment | -| @xfail| Prevents the test from running in workflows | +| Tag | Description | +|-----------------|-----------------------------------------------------| +| @dev | Runs the test in the DEV environment | +| @xfail | Prevents the test from running in workflows | +| @dont_run_prod | Prevents the test from running in prod workflow | ## Naming `data-testid` diff --git a/cypress/e2e/domains/create-domain-edge-application.cy.js b/cypress/e2e/domains/create-domain-edge-application.cy.js index c5e1030fd..87acce9c7 100644 --- a/cypress/e2e/domains/create-domain-edge-application.cy.js +++ b/cypress/e2e/domains/create-domain-edge-application.cy.js @@ -20,7 +20,7 @@ const createEdgeApplicationCase = () => { cy.get(selectors.domains.pageTitle(edgeAppName)).should('have.text', edgeAppName) } -describe('Domains spec', { tags: ['@dev3'] }, () => { +describe('Domains spec', { tags: ['@dev3', '@xfail'] }, () => { beforeEach(() => { cy.login() }) diff --git a/cypress/e2e/edge-dns/create-edge-dns-zone.cy.js b/cypress/e2e/edge-dns/create-edge-dns-zone.cy.js index 1eba90a45..e29536ff8 100644 --- a/cypress/e2e/edge-dns/create-edge-dns-zone.cy.js +++ b/cypress/e2e/edge-dns/create-edge-dns-zone.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev4'] }, () => { +describe('Edge DNS spec', { tags: ['@dev4', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-a.cy.js b/cypress/e2e/edge-dns/create-record-type-a.cy.js index 8448eb04e..b16fa897d 100644 --- a/cypress/e2e/edge-dns/create-record-type-a.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-a.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev4'] }, () => { +describe('Edge DNS spec', { tags: ['@dev4', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-aaaa.cy.js b/cypress/e2e/edge-dns/create-record-type-aaaa.cy.js index 38d3d9fb7..1dc932aa5 100644 --- a/cypress/e2e/edge-dns/create-record-type-aaaa.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-aaaa.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev4'] }, () => { +describe('Edge DNS spec', { tags: ['@dev4', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-aname.cy.js b/cypress/e2e/edge-dns/create-record-type-aname.cy.js index d822fe8c8..de5e165cf 100644 --- a/cypress/e2e/edge-dns/create-record-type-aname.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-aname.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev4'] }, () => { +describe('Edge DNS spec', { tags: ['@dev4', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-caa.cy.js b/cypress/e2e/edge-dns/create-record-type-caa.cy.js index ffdd702d7..0e74ff8b1 100644 --- a/cypress/e2e/edge-dns/create-record-type-caa.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-caa.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-cname.cy.js b/cypress/e2e/edge-dns/create-record-type-cname.cy.js index b21497c96..bfcf1182c 100644 --- a/cypress/e2e/edge-dns/create-record-type-cname.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-cname.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-ds.cy.js b/cypress/e2e/edge-dns/create-record-type-ds.cy.js index c24b03962..479535ea2 100644 --- a/cypress/e2e/edge-dns/create-record-type-ds.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-ds.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-mx.cy.js b/cypress/e2e/edge-dns/create-record-type-mx.cy.js index 117a1e56b..02a9c9df9 100644 --- a/cypress/e2e/edge-dns/create-record-type-mx.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-mx.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-ns.cy.js b/cypress/e2e/edge-dns/create-record-type-ns.cy.js index b55a4eca5..61d2e2308 100644 --- a/cypress/e2e/edge-dns/create-record-type-ns.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-ns.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-ptr.cy.js b/cypress/e2e/edge-dns/create-record-type-ptr.cy.js index 5a381a9ac..d4a041599 100644 --- a/cypress/e2e/edge-dns/create-record-type-ptr.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-ptr.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-srv.cy.js b/cypress/e2e/edge-dns/create-record-type-srv.cy.js index 7d8d8f317..fac431517 100644 --- a/cypress/e2e/edge-dns/create-record-type-srv.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-srv.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-dns/create-record-type-txt.cy.js b/cypress/e2e/edge-dns/create-record-type-txt.cy.js index a839522ac..0073b541b 100644 --- a/cypress/e2e/edge-dns/create-record-type-txt.cy.js +++ b/cypress/e2e/edge-dns/create-record-type-txt.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let zoneName = '' -describe('Edge DNS spec', { tags: ['@dev5'] }, () => { +describe('Edge DNS spec', { tags: ['@dev5', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() zoneName = generateUniqueName('DNSZone') diff --git a/cypress/e2e/edge-firewall/create-edge-firewall-function.cy.js b/cypress/e2e/edge-firewall/create-edge-firewall-function.cy.js index 40390e777..5371d5204 100644 --- a/cypress/e2e/edge-firewall/create-edge-firewall-function.cy.js +++ b/cypress/e2e/edge-firewall/create-edge-firewall-function.cy.js @@ -4,7 +4,7 @@ import selectors from '../../support/selectors' let firewallName, functionInstanceName, ruleName -describe('Edge Firewall spec', { tags: ['@dev5'] }, () => { +describe('Edge Firewall spec', { tags: ['@dev5', '@xfail'] }, () => { beforeEach(() => { cy.login() firewallName = generateUniqueName('EdgeFirewall') diff --git a/cypress/e2e/edge-firewall/create-edge-firewall-network-list.cy.js b/cypress/e2e/edge-firewall/create-edge-firewall-network-list.cy.js index 6dbcb65d8..9f51827e4 100644 --- a/cypress/e2e/edge-firewall/create-edge-firewall-network-list.cy.js +++ b/cypress/e2e/edge-firewall/create-edge-firewall-network-list.cy.js @@ -23,7 +23,7 @@ const createASNNetworkListCase = () => { cy.verifyToast('success', 'Your network list has been created') } -describe('Edge Firewall spec', { tags: ['@dev5'] }, () => { +describe('Edge Firewall spec', { tags: ['@dev5', '@xfail'] }, () => { beforeEach(() => { cy.login() firewallName = generateUniqueName('EdgeFirewall') diff --git a/cypress/e2e/network-lists/create-network-list-asn.cy.js b/cypress/e2e/network-lists/create-network-list-asn.cy.js index 2c156d4ea..f04c018be 100644 --- a/cypress/e2e/network-lists/create-network-list-asn.cy.js +++ b/cypress/e2e/network-lists/create-network-list-asn.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let networkListName -describe('Network Lists spec', { tags: ['@dev6'] }, () => { +describe('Network Lists spec', { tags: ['@dev6', '@xfail'] }, () => { beforeEach(() => { cy.login() cy.openProduct('Network Lists') diff --git a/cypress/e2e/network-lists/create-network-list-countries.cy.js b/cypress/e2e/network-lists/create-network-list-countries.cy.js index 9ecb0c51a..0c826cc9a 100644 --- a/cypress/e2e/network-lists/create-network-list-countries.cy.js +++ b/cypress/e2e/network-lists/create-network-list-countries.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let networkListName -describe('Network Lists spec', { tags: ['@dev6'] }, () => { +describe('Network Lists spec', { tags: ['@dev6', '@xfail'] }, () => { beforeEach(() => { cy.login() cy.openProduct('Network Lists') diff --git a/cypress/e2e/variables/create-secret.cy.js b/cypress/e2e/variables/create-secret.cy.js index 5c2022a5c..e907bcba9 100644 --- a/cypress/e2e/variables/create-secret.cy.js +++ b/cypress/e2e/variables/create-secret.cy.js @@ -4,7 +4,7 @@ import selectors from '../../support/selectors' let variableKey let variableValue -describe('Variables spec', { tags: ['@dev2'] }, () => { +describe('Variables spec', { tags: ['@dev2', '@xfail'] }, () => { beforeEach(() => { cy.login() cy.openProduct('Variables') diff --git a/cypress/e2e/waf/create-allowed-rule-conditional-query-string.cy.js b/cypress/e2e/waf/create-allowed-rule-conditional-query-string.cy.js index 4e4aed8b3..fe6df29bb 100644 --- a/cypress/e2e/waf/create-allowed-rule-conditional-query-string.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-conditional-query-string.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-allowed-rule-conditional-request-body.cy.js b/cypress/e2e/waf/create-allowed-rule-conditional-request-body.cy.js index 88db9c61c..9ecf7d8b0 100644 --- a/cypress/e2e/waf/create-allowed-rule-conditional-request-body.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-conditional-request-body.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-allowed-rule-conditional-request-header.cy.js b/cypress/e2e/waf/create-allowed-rule-conditional-request-header.cy.js index 540f24dd8..61c83043c 100644 --- a/cypress/e2e/waf/create-allowed-rule-conditional-request-header.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-conditional-request-header.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-allowed-rule-file-name.cy.js b/cypress/e2e/waf/create-allowed-rule-file-name.cy.js index 514442f88..650e3d4f3 100644 --- a/cypress/e2e/waf/create-allowed-rule-file-name.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-file-name.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-allowed-rule-path-zone.cy.js b/cypress/e2e/waf/create-allowed-rule-path-zone.cy.js index 42518c231..9c25bc16a 100644 --- a/cypress/e2e/waf/create-allowed-rule-path-zone.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-path-zone.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-allowed-rule-raw-body.cy.js b/cypress/e2e/waf/create-allowed-rule-raw-body.cy.js index df5a94fa3..4873bac03 100644 --- a/cypress/e2e/waf/create-allowed-rule-raw-body.cy.js +++ b/cypress/e2e/waf/create-allowed-rule-raw-body.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/create-waf-rule.cy.js b/cypress/e2e/waf/create-waf-rule.cy.js index f67064861..f391a9a5b 100644 --- a/cypress/e2e/waf/create-waf-rule.cy.js +++ b/cypress/e2e/waf/create-waf-rule.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/cypress/e2e/waf/edit-waf-rule.cy.js b/cypress/e2e/waf/edit-waf-rule.cy.js index dc410dfd9..4027687e1 100644 --- a/cypress/e2e/waf/edit-waf-rule.cy.js +++ b/cypress/e2e/waf/edit-waf-rule.cy.js @@ -3,7 +3,7 @@ import selectors from '../../support/selectors' let wafName -describe('WAF spec', { tags: ['@dev7'] }, () => { +describe('WAF spec', { tags: ['@dev7', '@dont_run_prod'] }, () => { beforeEach(() => { cy.login() cy.openProduct('WAF Rules') diff --git a/package.json b/package.json index 4431ce5ef..cabf9bfae 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "test:e2e:run:dev": "start-server-and-test 'vite dev --port 5173' http://localhost:5173 'cypress run --e2e'", "test:e2e:run:preview-dev": "start-server-and-test preview http://localhost:5173 'cypress run --e2e'", "test:e2e:run:stage": "cypress run --e2e --config-file cypress.config.stage.js --env environment=stage", - "test:e2e:run:prod": "cypress run --e2e --config-file cypress.config.prod.js --env environment=prod", + "test:e2e:run:prod": "cypress run --e2e --config-file cypress.config.prod.js --env environment=prod,grepTags=-@dont_run_prod", "test:e2e:run:preview-prod": "cypress run --e2e --config-file cypress.config.preview-prod.js --env environment=preview-prod", "test:e2e:open:dev": "start-server-and-test 'vite dev --port 5173' http://localhost:5173 'cypress open --e2e'", "test:e2e:open:preview-dev": "start-server-and-test preview http://localhost:5173 'cypress open --e2e'", "test:e2e:open:stage": "cypress open --e2e --config-file cypress.config.stage.js --env environment=stage", - "test:e2e:open:prod": "cypress open --e2e --config-file cypress.config.prod.js --env environment=prod", + "test:e2e:open:prod": "cypress open --e2e --config-file cypress.config.prod.js --env environment=prod,grepTags=-@dont_run_prod", "test:e2e:open:preview-prod": "cypress open --e2e --config-file cypress.config.preview-prod.js --env environment=preview-prod", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", "security-check": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore --ignore-pattern 'cypress/**' --ignore-pattern 'cypress.config.*' --ignore-pattern '**/*.test.js' -c .eslintrc-security.cjs", diff --git a/scripts/cleanup.js b/scripts/cleanup.js new file mode 100644 index 000000000..2fbd6dbb3 --- /dev/null +++ b/scripts/cleanup.js @@ -0,0 +1,189 @@ +import axios from 'axios'; +import fs from 'fs'; +import path from 'path'; +import process from 'process'; +import console from 'console'; +import { setTimeout } from 'timers/promises'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const readCypressEnv = () => { + const cypressEnvPath = path.resolve(__dirname, '../cypress.env.json'); + if (fs.existsSync(cypressEnvPath)) { + const rawdata = fs.readFileSync(cypressEnvPath); + return JSON.parse(rawdata); + } else { + console.error('⚠️ cypress.env.json file not found.'); + process.exit(1); + } +}; + +const cypressEnv = process.env.CI ? {} : readCypressEnv(); + + +const ENV = process.argv[2] ? process.argv[2].toUpperCase() : 'STAGE'; +const API_v3 = + ENV === 'PROD' ? 'https://api.azionapi.net' : 'https://stage-api.azion.net'; +const API_v4 = + ENV === 'PROD' + ? 'https://api.azion.com/v4' + : 'https://stage-api.azion.com/v4'; +const URL = `${API_v3}`; +const URL_v4 = `${API_v4}`; + +const CYPRESS_TOKEN = process.env.CI + ? process.env[`${ENV}_CYPRESS_TOKEN`] + : cypressEnv[`${ENV}_CYPRESS_TOKEN`]; + +const credentials = { + cypress: { token: CYPRESS_TOKEN, wait_time: 10 } +}; + +const entities = [ + { name: 'credentials', url: `${URL}/credentials`, version: 3 }, + { + name: 'data_streaming', + url: `${URL}/data_streaming/streamings`, + version: 3 + }, + { + name: 'edge_applications', + url: `${URL}/edge_applications`, + version: 3, + exclude: [1718380244, 340244] + }, + { name: 'edge_firewall', url: `${URL}/edge_firewall`, version: 3 }, + { name: 'edge_sql', url: `${URL_v4}/edge_sql/databases`, version: 4 }, + { name: 'waf_rulesets', url: `${URL}/waf/rulesets`, version: 3 }, + { + name: 'digital_certificates', + url: `${URL}/digital_certificates/`, + version: 3 + }, + { + name: 'network_lists', + url: `${URL}/network_lists`, + version: 3, + exclude: [66, 2] + } +]; + +const deleteResources = async ( + url, + headers, + wait_time, + getCount, + getResults, + getSingleUrl, + excludeIds = [] +) => { + let hasResource = true; + + while (hasResource) { + const response = await axios.get(url, { headers }); + const count = getCount(response.data); + const results = getResults(response.data); + + console.log(`\n🗑️ Total of ${count} resources to be deleted.`); + + if (count === 0) { + console.log('✅ No more resources to delete.'); + break; + } + + hasResource = results.length > 0; + + if (!hasResource) { + console.log('✅ No more resources to delete.'); + break; + } + + let deletedCount = 0; + + for (const resource of results) { + if (!excludeIds.includes(resource.id)) { + try { + const singleResourceUrl = getSingleUrl(resource); + const deleteResponse = await axios.delete(singleResourceUrl, { + headers + }); + console.log( + `🗑️ Deleted resource: ${singleResourceUrl} - Status code: ${deleteResponse.status}` + ); + deletedCount++; + } catch (error) { + console.error( + `❌ Error deleting resource with ID ${resource.id}: ${error.message}` + ); + } + } else { + console.log(`🚫 Skipping deletion for resource with ID ${resource.id}`); + } + } + + if (deletedCount === 0) { + console.log('✅ No resources deleted in this iteration.'); + break; + } + + console.log(`⏳ Waiting for ${wait_time} seconds before continuing...`); + await setTimeout(wait_time * 1000); + } +}; + +const getCountFunctions = { + credentials: data => (data.credentials ? data.credentials.length : 0), + data_streaming: data => (data.results ? data.results.length : 0), + edge_applications: data => (data.count ? data.count : 0), + edge_firewall: data => (data.count ? data.count : 0), + edge_sql: data => (data.count ? data.count : 0), + waf_rulesets: data => (data.count ? data.count : 0), + digital_certificates: data => (data.count ? data.count : 0), + network_lists: data => (data.count ? data.count : 0) +}; + +const getResultsFunctions = { + credentials: data => data.credentials || [], + data_streaming: data => data.results || [], + edge_applications: data => data.results || [], + edge_firewall: data => data.results || [], + edge_sql: data => data.results || [], + waf_rulesets: data => data.results || [], + digital_certificates: data => data.results || [], + network_lists: data => data.results || [] +}; + +(async () => { + for (const userType in credentials) { + const { token, wait_time } = credentials[userType]; + const headers = { + Accept: 'application/json; version=3', + 'Content-Type': 'application/json', + Authorization: `token ${token}` + }; + + for (const entity of entities) { + console.log(`\n🔄 Processing ${entity.name}...`); + const getCount = getCountFunctions[entity.name.split('/')[0]]; + const getResults = getResultsFunctions[entity.name.split('/')[0]]; + + if (!getCount || !getResults) { + console.error(`⚠️ No functions found for entity: ${entity.name}`); + continue; + } + + await deleteResources( + entity.url, + headers, + wait_time, + getCount, + getResults, + resource => `${entity.url}/${resource.id}`, + entity.exclude || [] + ); + } + } +})(); diff --git a/src/App.vue b/src/App.vue index f258a44d7..36f103044 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,13 +19,17 @@ const route = useRoute() const updateTrackingTraits = () => { - const { kind: accountType, client_id: clientId, email, name } = accountStore.account - const isAccountTypeWithoutClientId = accountType !== 'client' - if (isAccountTypeWithoutClientId) return - - const defaultTraits = { client_id: clientId, email, name } + const { + user_id: userID, + id: accountId, + client_id: clientId, + email, + name + } = accountStore.account + + const defaultTraits = { client_id: clientId, email, name, account_id: accountId } tracker.assignGroupTraits(defaultTraits) - tracker.identify(clientId) + tracker.identify(userID) } const isLogged = computed(() => { diff --git a/src/main.js b/src/main.js index 8bd665807..e8273e029 100644 --- a/src/main.js +++ b/src/main.js @@ -22,7 +22,6 @@ import { install as VueMonacoEditorPlugin } from '@guolao/vue-monaco-editor' import * as HelpCenterServices from '@/services/help-center-services' import DialogService from 'primevue/dialogservice' import { customAiPrompt } from '@modules/azion-ai-chat/directives/custom-ai-prompt' -import StripeIntegrationPlugin from '@/plugins/StripeIntegrationPlugin' import TrackerPlugin from '@/plugins/AnalyticsTrackerAdapterPlugin' @@ -42,7 +41,6 @@ app.use(pinia) app.use(router) app.use(DialogService) app.use(TrackerPlugin) -app.use(StripeIntegrationPlugin) app.use(VueMonacoEditorPlugin, { paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.38.0/min/vs' diff --git a/src/plugins/StripeIntegrationPlugin.js b/src/plugins/StripeIntegrationPlugin.js deleted file mode 100644 index 7c94f8808..000000000 --- a/src/plugins/StripeIntegrationPlugin.js +++ /dev/null @@ -1,23 +0,0 @@ -import { getEnvironment } from '@/helpers' -import { makeStripeClient } from './factories/stripe-integration-factory' - -/**@type {import('vue').Plugin} */ -export default { - // eslint-disable-next-line no-unused-vars - install: (Vue, options) => { - const environment = getEnvironment() - - let stripeClient - try { - stripeClient = makeStripeClient(environment) - } catch (error) { - // eslint-disable-next-line no-console - console.error(error) - return - } - - const app = Vue - app.config.globalProperties.$stripe = stripeClient - app.provide('stripe', stripeClient) - } -} diff --git a/src/plugins/analytics/AnalyticsTrackerAdapter.js b/src/plugins/analytics/AnalyticsTrackerAdapter.js index 4200d0e05..2d98fefa5 100644 --- a/src/plugins/analytics/AnalyticsTrackerAdapter.js +++ b/src/plugins/analytics/AnalyticsTrackerAdapter.js @@ -88,9 +88,21 @@ export class AnalyticsTrackerAdapter { * @return {Promise} */ async identify(id) { - if (!id || !this.#hasAnalytics()) return + const userId = `${id}` + if (!userId || !this.#hasAnalytics()) return - await this.#analyticsClient.identify(id, this.#traits) + await this.#analyticsClient.identify(userId, this.#traits) + } + + /** + * Resets the internal state of the tracker, including any queued events and traits. + */ + reset() { + this.#events = [] + this.#traits = {} + if (this.#hasAnalytics()) { + this.#analyticsClient.reset() + } } /** diff --git a/src/plugins/analytics/trackers/ProductTracker.js b/src/plugins/analytics/trackers/ProductTracker.js index df5940e70..7efd057c8 100644 --- a/src/plugins/analytics/trackers/ProductTracker.js +++ b/src/plugins/analytics/trackers/ProductTracker.js @@ -54,6 +54,21 @@ export class ProductTracker { return this.#trackerAdapter } + /** + * @param {Object} payload + * @param {String} productName + * @returns {AnalyticsTrackerAdapter} + */ + clickedOnEvent(productName, payload) { + this.#trackerAdapter.addEvent({ + eventName: `Clicked on ${productName}`, + props: { + ...payload + } + }) + return this.#trackerAdapter + } + /** * @param {Object} payload * @param {AzionProductsNames} payload.productName diff --git a/src/plugins/factories/stripe-integration-factory.js b/src/plugins/factories/stripe-integration-factory.js deleted file mode 100644 index 34d91ae11..000000000 --- a/src/plugins/factories/stripe-integration-factory.js +++ /dev/null @@ -1,30 +0,0 @@ -import { loadStripe } from '@stripe/stripe-js' - -export function makeStripeClient(environment) { - const stripeEnvVarName = { - development: 'VITE_STRIPE_TOKEN_DEV', - stage: 'VITE_STRIPE_TOKEN_STAGE', - production: 'VITE_STRIPE_TOKEN_PROD' - } - - const enviromentStripeToken = stripeEnvVarName[environment] - - if (!environment) { - throw Error('Provide an environment to select correct tracking token') - } - const isInvalidEnvironment = !['development', 'stage', 'production'].includes(environment) - if (isInvalidEnvironment) { - throw Error('Provide an valid environment to select correct tracking token') - } - - const stripeToken = import.meta.env[enviromentStripeToken] - if (!stripeToken) { - throw Error('Stripe token is missing, cannot load Stripe. View readme for more info.') - } - - const stripePromise = loadStripe(stripeToken, { - locale: 'en' - }) - - return stripePromise -} diff --git a/src/router/hooks/beforeEachRoute.js b/src/router/hooks/beforeEachRoute.js index eed025ce1..86877eb09 100644 --- a/src/router/hooks/beforeEachRoute.js +++ b/src/router/hooks/beforeEachRoute.js @@ -1,18 +1,36 @@ -import * as guards from '@/router/hooks/guards' +import { + logoutGuard, + loadingGuard, + accountGuard, + themeGuard, + billingGuard, + redirectGuard, + redirectToManagerGuard +} from '@/router/hooks/guards' import { useHelpCenterStore } from '@/stores/help-center' import { useRouter } from 'vue-router' -/** @type {import('vue-router').NavigationGuardWithThis} */ -export default async function beforeEachRoute(to, from, next) { - const helpCenterStore = useHelpCenterStore() - helpCenterStore.close() +export default async function beforeEachRoute(guardDependency) { + useHelpCenterStore().close() const router = useRouter() - await guards.logoutGuard(to, next) - guards.loadingGuard(to) - await guards.accountGuard(to, next) - guards.themeGuard() - guards.billingGuard(to, next) - guards.redirectGuard(to, next, router) + const { next } = guardDependency + + const guards = [ + logoutGuard, + loadingGuard, + accountGuard, + themeGuard, + billingGuard, + redirectGuard, + redirectToManagerGuard + ] + + for (const executeGuard of guards) { + const result = await executeGuard({ ...guardDependency, router }) + if (result !== undefined) { + return next(result) + } + } return next() } diff --git a/src/router/hooks/guards/accountGuard.js b/src/router/hooks/guards/accountGuard.js index 0e5ef9f3f..995677c8f 100644 --- a/src/router/hooks/guards/accountGuard.js +++ b/src/router/hooks/guards/accountGuard.js @@ -1,11 +1,9 @@ import { getAccountInfoService, getUserInfoService } from '@/services/account-services' import { loadAccountJobRoleService } from '@/services/account-settings-services' -import { useAccountStore } from '@/stores/account' import { setRedirectRoute } from '@/helpers' /** @type {import('vue-router').NavigationGuardWithThis} */ -export async function accountGuard(to, next) { - const accountStore = useAccountStore() +export async function accountGuard({ to, accountStore, tracker }) { const isPrivateRoute = !to.meta.isPublic const userNotIsLoggedIn = !accountStore.hasActiveUserId @@ -30,7 +28,8 @@ export async function accountGuard(to, next) { accountStore.setAccountData(accountInfo) } catch { setRedirectRoute(to) - return next('/login') + await tracker.reset() + return '/login' } } } diff --git a/src/router/hooks/guards/billingGuard.js b/src/router/hooks/guards/billingGuard.js index 5de31090f..0f940c5ac 100644 --- a/src/router/hooks/guards/billingGuard.js +++ b/src/router/hooks/guards/billingGuard.js @@ -1,4 +1,3 @@ -import { useAccountStore } from '@/stores/account' import { billingRoutes } from '@/router/routes/billing-routes' import { getStaticUrlsByEnvironment } from '@/helpers' @@ -10,8 +9,7 @@ const BILLING_REDIRECT_OPTIONS = { const billingUrl = getStaticUrlsByEnvironment('billing') /** @type {import('vue-router').NavigationGuardWithThis} */ -export async function billingGuard(to, next) { - const accountStore = useAccountStore() +export async function billingGuard({ to, accountStore }) { const { hasActiveUserId, redirectToExternalBillingNeeded, @@ -26,14 +24,18 @@ export async function billingGuard(to, next) { if (isCurrentRouteBilling) { if (redirectToExternalBillingNeeded) { window.open(billingUrl, '_blank') - return next(false) + return false } if (!billingAccessPermitted) { - return next('/') + return '/' + } + + if (to.name === 'billing-tabs') { + return true } } else if (paymentReviewPending) { - return next(BILLING_REDIRECT_OPTIONS) + return BILLING_REDIRECT_OPTIONS } } } diff --git a/src/router/hooks/guards/index.js b/src/router/hooks/guards/index.js index 6002bee1d..4e36d1b7f 100644 --- a/src/router/hooks/guards/index.js +++ b/src/router/hooks/guards/index.js @@ -4,5 +4,14 @@ import { accountGuard } from './accountGuard' import { themeGuard } from './themeGuard' import { billingGuard } from './billingGuard' import { redirectGuard } from './redirectGuard' +import { redirectToManagerGuard } from './redirectToManagerGuard' -export { logoutGuard, loadingGuard, accountGuard, themeGuard, billingGuard, redirectGuard } +export { + logoutGuard, + loadingGuard, + accountGuard, + themeGuard, + billingGuard, + redirectGuard, + redirectToManagerGuard +} diff --git a/src/router/hooks/guards/loadingGuard.js b/src/router/hooks/guards/loadingGuard.js index dc4758842..25fd913c9 100644 --- a/src/router/hooks/guards/loadingGuard.js +++ b/src/router/hooks/guards/loadingGuard.js @@ -1,7 +1,7 @@ import { useLoadingStore } from '@/stores/loading' /** @type {import('vue-router').NavigationGuardWithThis} */ -export async function loadingGuard(to) { +export async function loadingGuard({ to }) { const loadingStore = useLoadingStore() loadingStore.startLoading() diff --git a/src/router/hooks/guards/logoutGuard.js b/src/router/hooks/guards/logoutGuard.js index 238085e91..2520f761c 100644 --- a/src/router/hooks/guards/logoutGuard.js +++ b/src/router/hooks/guards/logoutGuard.js @@ -1,12 +1,11 @@ import { logoutService } from '@/services/auth-services' -import { useAccountStore } from '@/stores/account' /** @type {import('vue-router').NavigationGuardWithThis} */ -export async function logoutGuard(to, next) { +export async function logoutGuard({ to, accountStore, tracker }) { if (to.path === '/logout') { + tracker.reset() await logoutService() - const accountStore = useAccountStore() accountStore.setAccountData({}) - return next() + return true } } diff --git a/src/router/hooks/guards/redirectGuard.js b/src/router/hooks/guards/redirectGuard.js index 87d2e1b40..7ada5ad6d 100644 --- a/src/router/hooks/guards/redirectGuard.js +++ b/src/router/hooks/guards/redirectGuard.js @@ -1,11 +1,11 @@ import { getRedirectRoute } from '@/helpers' /** @type {import('vue-router').NavigationGuardWithThis} */ -export function redirectGuard(to, next, router) { +export function redirectGuard({ to, router }) { if (to.name === 'home') { const redirectRoute = getRedirectRoute(router) if (redirectRoute) { - return next(redirectRoute) + return redirectRoute } } } diff --git a/src/router/hooks/redirectToManager.js b/src/router/hooks/guards/redirectToManagerGuard.js similarity index 67% rename from src/router/hooks/redirectToManager.js rename to src/router/hooks/guards/redirectToManagerGuard.js index 9ae386530..139bf5614 100644 --- a/src/router/hooks/redirectToManager.js +++ b/src/router/hooks/guards/redirectToManagerGuard.js @@ -1,30 +1,26 @@ import { getEnvironment, getStaticUrlsByEnvironment } from '@/helpers' -import { loadContractServicePlan } from '@/services/contract-services' -import { useAccountStore } from '@/stores/account' -/** @type {import('vue-router').NavigationGuardWithThis} */ -export default async function redirectToManager(to, from, next) { - const accountStore = useAccountStore() +export async function redirectToManagerGuard({ to, from, accountStore, loadContractServicePlan }) { const isPrivateRoute = !to.meta.isPublic const accountData = accountStore.accountData try { if (accountStore.hasActiveUserId && isPrivateRoute) { - if (accountStore.metricsOnlyAccessRestriction) { - return handleNavigationRestriction(to, from, next) - } - const isAzion = accountData.email.includes('@azion.com') - // Azion internal access to console. + if (isAzion || accountStore.hasAccessConsole) { - return next() + return true } + + if (accountStore.metricsOnlyAccessRestriction) { + return handleNavigationRestriction(to, from) + } + const isNotClientKind = !accountData.client_id // [Brand,Reseller,Group] are the kins without client id. - if (isNotClientKind && !isAzion) { - permanentRedirectToManager() + if (isNotClientKind) { + return permanentRedirectToManager() //ensure that in development env, without redirect, you continue to next route. - return next() } // account that are kind client, can access with developer service plan @@ -37,13 +33,13 @@ export default async function redirectToManager(to, from, next) { }) if (!isDeveloperSupportPlan) { - permanentRedirectToManager() + return permanentRedirectToManager() } } } catch { - return next() + return true } - return next() + return true } function permanentRedirectToManager() { @@ -51,20 +47,21 @@ function permanentRedirectToManager() { if (environment !== 'development') { const managerUrl = getStaticUrlsByEnvironment('manager') window.location.replace(managerUrl) + return true } } -function handleNavigationRestriction(to, from, next) { +function handleNavigationRestriction(to, from) { const metricsRouteName = 'real-time-metrics' const isNotNavigatingToMetrics = to.name !== metricsRouteName const isNavigatingFromMetrics = from.name === metricsRouteName if (isNotNavigatingToMetrics) { if (isNavigatingFromMetrics) { - return next(false) + return false } - return next({ name: metricsRouteName }) + return { name: metricsRouteName } } - return next() + return true } diff --git a/src/router/hooks/guards/themeGuard.js b/src/router/hooks/guards/themeGuard.js index c57c6d2eb..b85a68f9c 100644 --- a/src/router/hooks/guards/themeGuard.js +++ b/src/router/hooks/guards/themeGuard.js @@ -1,8 +1,4 @@ -import { useAccountStore } from '@/stores/account' - -export async function themeGuard() { - const accountStore = useAccountStore() - +export async function themeGuard({ accountStore }) { // TODO: remove the usage of localStorage when API returns the theme const theme = localStorage.getItem('theme') const fallbackTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' diff --git a/src/router/index.js b/src/router/index.js index 123d617fb..e90e312f9 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -1,3 +1,4 @@ +import { inject } from 'vue' import { accountRoutes } from '@routes/account-routes' import { activityHistoryRoutes } from '@routes/activity-history-routes' import { azionAiRoutes } from '@routes/azion-ai-routes' @@ -40,7 +41,8 @@ import { billingRoutes } from '@/router/routes/billing-routes' import { createRouter, createWebHistory } from 'vue-router' import afterEachRouteGuard from './hooks/afterEachRoute' import beforeEachRoute from './hooks/beforeEachRoute' -import redirectToManager from './hooks/redirectToManager' +import { useAccountStore } from '@/stores/account' +import { loadContractServicePlan } from '@/services/contract-services' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -85,8 +87,21 @@ const router = createRouter({ ].concat(errorRoutes) }) -router.beforeEach(beforeEachRoute) -router.beforeEach(redirectToManager) +router.beforeEach(async (to, from, next) => { + const accountStore = useAccountStore() + /** @type {import('@/plugins/analytics/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */ + const tracker = inject('tracker') + + await beforeEachRoute({ + to, + from, + next, + accountStore, + loadContractServicePlan, + tracker + }) +}) + router.afterEach(afterEachRouteGuard) export default router diff --git a/src/router/routes/billing-routes/index.js b/src/router/routes/billing-routes/index.js index e72ee39c8..b39ce5eb6 100644 --- a/src/router/routes/billing-routes/index.js +++ b/src/router/routes/billing-routes/index.js @@ -17,6 +17,7 @@ export const billingRoutes = { loadPaymentMethodDefaultService: BillingServices.loadPaymentMethodDefaultService, addCreditService: BillingServices.addCreditService, createPaymentMethodService: BillingServices.createPaymentMethodService, + getStripeClientService: BillingServices.getStripeClientService, paymentServices: { listPaymentMethodsService: BillingServices.listPaymentMethodsService, deletePaymentService: BillingServices.deletePaymentService, diff --git a/src/services/billing-services/get-stripe-client-service.js b/src/services/billing-services/get-stripe-client-service.js new file mode 100644 index 000000000..b5a9cca1f --- /dev/null +++ b/src/services/billing-services/get-stripe-client-service.js @@ -0,0 +1,45 @@ +import { getEnvironment } from '@/helpers' +import { loadStripe } from '@stripe/stripe-js/pure' + +const makeStripeClient = async (environment) => { + const stripeEnvVarName = { + development: 'VITE_STRIPE_TOKEN_DEV', + stage: 'VITE_STRIPE_TOKEN_STAGE', + production: 'VITE_STRIPE_TOKEN_PROD' + } + + const enviromentStripeToken = stripeEnvVarName[environment] + + const isInvalidEnvironment = !['development', 'stage', 'production'].includes(environment) + if (isInvalidEnvironment) { + throw Error('Provide a valid environment to select correct tracking token') + } + + const stripeToken = import.meta.env[enviromentStripeToken] + if (!stripeToken) { + throw Error('Stripe token is missing, cannot load Stripe. View readme for more info.') + } + + if (environment !== 'production') { + /** + * This avoids calling the endpoint m.stripe.com when not in production + * For more information see: https://docs.stripe.com/disputes/prevention/advanced-fraud-detection#disabling-advanced-fraud-detection + **/ + loadStripe.setLoadParameters({ advancedFraudSignals: false }) + } + + const stripeClient = await loadStripe(stripeToken, { + locale: 'en' + }) + + return stripeClient +} + +export const getStripeClientService = async () => { + const environment = getEnvironment() + try { + return await makeStripeClient(environment) + } catch (error) { + throw new Error(error.message).message + } +} diff --git a/src/services/billing-services/index.js b/src/services/billing-services/index.js index 900c3cfc6..7567bfad2 100644 --- a/src/services/billing-services/index.js +++ b/src/services/billing-services/index.js @@ -11,6 +11,7 @@ import { loadInvoiceDataService } from './load-invoice-data-service' import { loadPaymentMethodDefaultService } from './load-payment-method-default-service' import { listServiceAndProductsChangesService } from './list-service-and-products-changes' import { loadInvoiceLastUpdatedService } from './load-invoice-last-updated-service' +import { getStripeClientService } from './get-stripe-client-service' export { listPaymentMethodsService, @@ -25,5 +26,6 @@ export { loadInvoiceDataService, loadPaymentMethodDefaultService, listServiceAndProductsChangesService, - loadInvoiceLastUpdatedService + loadInvoiceLastUpdatedService, + getStripeClientService } diff --git a/src/services/edge-node-service-services/create-service-edge-node-service.js b/src/services/edge-node-service-services/create-service-edge-node-service.js index 2a5e3769d..89bea9926 100644 --- a/src/services/edge-node-service-services/create-service-edge-node-service.js +++ b/src/services/edge-node-service-services/create-service-edge-node-service.js @@ -29,7 +29,7 @@ const parseCodeToVariables = (code) => { const adapt = (payload) => { const variables = parseCodeToVariables(payload.variables) return { - service_id: payload.service.serviceId, + service_id: payload.serviceId, variables } } diff --git a/src/services/edge-node-service-services/load-service-edge-node-service.js b/src/services/edge-node-service-services/load-service-edge-node-service.js index 8c1eb25af..6f7e25055 100644 --- a/src/services/edge-node-service-services/load-service-edge-node-service.js +++ b/src/services/edge-node-service-services/load-service-edge-node-service.js @@ -6,7 +6,6 @@ export const loadServiceEdgeNodeService = async ({ id, edgeNodeId }) => { url: `${makeEdgeNodeBaseUrl()}/${edgeNodeId}/services/${id}`, method: 'GET' }) - httpResponse = adapt(httpResponse, id) return parseHttpResponse(httpResponse) } @@ -20,11 +19,7 @@ const adapt = (httpResponse, id) => { const service = { id: id, - service: { - name: httpResponse.body.service_name, - serviceId: httpResponse.body.service_id, - id: httpResponse.body.id - }, + serviceId: httpResponse.body.service_id, variables: variables } diff --git a/src/services/network-lists-services/create-network-list-service.js b/src/services/network-lists-services/create-network-list-service.js index b093a54e8..74cd53c93 100644 --- a/src/services/network-lists-services/create-network-list-service.js +++ b/src/services/network-lists-services/create-network-list-service.js @@ -21,6 +21,19 @@ const adapt = (payload) => { } } +/** + * @param {Object} body - The response body. + * @returns {string} The result message based on the status code. + */ +const extractApiError = (body) => { + for (const keyError of Object.keys(body)) { + const errorValue = Array.isArray(body[keyError]) ? body[keyError][0] : body[keyError] + if (typeof errorValue === 'string') return errorValue + if (typeof errorValue === 'object' && errorValue.message) return errorValue.message[0] + } + return '' +} + /** * @param {Object} httpResponse - The HTTP response object. * @param {Object} httpResponse.body - The response body. @@ -36,7 +49,7 @@ const parseHttpResponse = (httpResponse) => { urlToEditView: `/network-lists/edit/${httpResponse.body.results.id}` } case 400: - const apiError = httpResponse.body.results[0] + const apiError = extractApiError(httpResponse.body) throw new Error(apiError).message case 401: throw new Errors.InvalidApiTokenError().message diff --git a/src/services/network-lists-services/edit-network-list-service.js b/src/services/network-lists-services/edit-network-list-service.js index 9b38b7aed..8396c7411 100644 --- a/src/services/network-lists-services/edit-network-list-service.js +++ b/src/services/network-lists-services/edit-network-list-service.js @@ -27,6 +27,19 @@ const adapt = (payload) => { } } +/** + * @param {Object} body - The response body. + * @returns {string} The result message based on the status code. + */ +const extractApiError = (body) => { + for (const keyError of Object.keys(body)) { + const errorValue = Array.isArray(body[keyError]) ? body[keyError][0] : body[keyError] + if (typeof errorValue === 'string') return errorValue + if (typeof errorValue === 'object' && errorValue.message) return errorValue.message[0] + } + return '' +} + /** * @param {Object} httpResponse - The HTTP response object. * @param {Object} httpResponse.body - The response body. @@ -39,7 +52,7 @@ const parseHttpResponse = (httpResponse) => { case 202: return 'Your network list has been edited' case 400: - const apiError = httpResponse.body.results[0] + const apiError = extractApiError(httpResponse.body) throw new Error(apiError).message case 401: throw new Errors.InvalidApiTokenError().message diff --git a/src/services/waf-rules-services/list-waf-rules-tuning-service.js b/src/services/waf-rules-services/list-waf-rules-tuning-service.js index 2632a32cc..6fb20bee6 100644 --- a/src/services/waf-rules-services/list-waf-rules-tuning-service.js +++ b/src/services/waf-rules-services/list-waf-rules-tuning-service.js @@ -1,5 +1,6 @@ -import { AxiosHttpClientAdapter, parseHttpResponse } from '../axios/AxiosHttpClientAdapter' +import { AxiosHttpClientAdapter } from '../axios/AxiosHttpClientAdapter' import { makeWafRulesBaseUrl } from './make-waf-rules-base-url' +import * as Errors from '@/services/axios/errors' export const listWafRulesTuningService = async ({ wafId, domains, network, hourRange, filter }) => { if (!wafId) { @@ -21,6 +22,15 @@ export const listWafRulesTuningService = async ({ wafId, domains, network, hourR return parseHttpResponse(httpResponse) } +/** + * @param {Object} httpResponse - The HTTP response object. + * @param {Object} httpResponse.body - The response body. + * @returns {string} The result message based on the status code. + */ +const extractApiError = (httpResponse) => { + return httpResponse.body.errors[0].message +} + const middleware = (filter) => { if (!filter?.length) return { countries: null, ipsList: null } @@ -54,9 +64,9 @@ const adapt = (httpResponse) => { * like other andpoints. */ - // eslint-disable-next-line no-console - const isArray = Array.isArray(httpResponse.body.results) + if (httpResponse.statusCode !== 200) return httpResponse + const isArray = Array.isArray(httpResponse.body.results) const parsedWafRulesTuning = isArray ? httpResponse.body.results.map((event) => { const values = { @@ -94,3 +104,19 @@ const makeSearchParams = ({ domains, network, countries, ipsList, hourRange }) = return searchParams } + +const parseHttpResponse = (httpResponse) => { + switch (httpResponse.statusCode) { + case 200: + return httpResponse.body + case 400: + const apiError = extractApiError(httpResponse) + throw new Error(apiError).message + case 404: + throw new Errors.NotFoundError().message + case 500: + throw new Errors.InternalServerError().message + default: + throw new Errors.UnexpectedError().message + } +} diff --git a/src/templates/add-payment-method-block/index.vue b/src/templates/add-payment-method-block/index.vue index 645f76b6e..bc9646615 100644 --- a/src/templates/add-payment-method-block/index.vue +++ b/src/templates/add-payment-method-block/index.vue @@ -1,5 +1,5 @@ diff --git a/src/views/DigitalCertificates/ListView.vue b/src/views/DigitalCertificates/ListView.vue index a5244b0fb..d6f6b9884 100644 --- a/src/views/DigitalCertificates/ListView.vue +++ b/src/views/DigitalCertificates/ListView.vue @@ -14,6 +14,8 @@ @on-load-data="handleLoadData" emptyListMessage="No digital certificates found." :actions="actions" + @on-before-go-to-add-page="handleTrackEventGoToCreate" + @on-before-go-to-edit="handleTrackEventGoToEdit" /> diff --git a/src/views/EdgeDNS/EditView.vue b/src/views/EdgeDNS/EditView.vue index 951f99a41..0bfda4906 100644 --- a/src/views/EdgeDNS/EditView.vue +++ b/src/views/EdgeDNS/EditView.vue @@ -12,7 +12,7 @@ import TabPanel from 'primevue/tabpanel' import TabView from 'primevue/tabview' import { useToast } from 'primevue/usetoast' - import { computed, onBeforeMount, ref, watch, reactive, provide } from 'vue' + import { computed, onBeforeMount, ref, watch, reactive, provide, inject } from 'vue' import { useRoute, useRouter } from 'vue-router' import * as yup from 'yup' import FormFieldsEdgeDnsCreate from './FormFields/FormFieldsEdgeDns.vue' @@ -20,6 +20,10 @@ import { generateCurrentTimestamp } from '@/helpers/generate-timestamp' import { columnBuilder } from '@/templates/list-table-block/columns/column-builder' import { TTL_MAX_VALUE_RECORDS } from '@/utils/constants' + import { handleTrackerError } from '@/utils/errorHandlingTracker' + + /**@type {import('@/plugins/analytics/AnalyticsTrackerAdapter').AnalyticsTrackerAdapter} */ + const tracker = inject('tracker') const props = defineProps({ loadEdgeDNSService: { type: Function, required: true }, @@ -156,6 +160,24 @@ hasContentToList.value = true } + const handleTrackEditEvent = () => { + tracker.product.productEdited({ + productName: 'Edge DNS Zone' + }) + } + + const handleTrackFailEditEvent = (error) => { + const { fieldName, message } = handleTrackerError(error) + tracker.product + .failedToEdit({ + productName: 'Edge DNS Zone', + errorType: 'api', + fieldName: fieldName.trim(), + errorMessage: message + }) + .track() + } + const listRecordsServiceEdgeDNSDecorator = async () => { return await props.listRecordsService({ id: edgeDNSID.value }) } @@ -169,6 +191,7 @@ const openCreateDrawerEDNSResource = () => { showCreateRecordDrawer.value = true + handleTrackEventGoToCreate() } const openEditDrawerEDNSResource = (event) => { selectedEdgeDnsRecordToEdit.value = event.id @@ -265,6 +288,61 @@ service: deleteRecordsServiceEdgeDNSDecorator } ] + + const handleTrackEventGoToCreate = () => { + tracker.product + .clickToCreate({ + productName: 'Record' + }) + .track() + } + + const handleTrackEventGoToEdit = () => { + tracker.product + .clickToEdit({ + productName: 'Record' + }) + .track() + } + + const handleTrackSuccessCreated = () => { + tracker.product + .productCreated({ + productName: 'Record' + }) + .track() + } + + const handleTrackFailCreated = (error) => { + const { fieldName, message } = handleTrackerError(error) + tracker.product + .failedToCreate({ + productName: 'Record', + errorType: 'api', + fieldName: fieldName.trim(), + errorMessage: message + }) + .track() + } + + const handleTrackSuccessEdit = () => { + tracker.product + .productEdited({ + productName: 'Record' + }) + .track() + } + const handleTrackFailEdit = (error) => { + const { fieldName, message } = handleTrackerError(error) + tracker.product + .failedToEdit({ + productName: 'Record', + errorType: 'api', + fieldName: fieldName.trim(), + errorMessage: message + }) + .track() + }