From b23ca5880e557029e51be95232f6351d554552bd Mon Sep 17 00:00:00 2001 From: Elliot Yibaebi Date: Thu, 23 Jan 2025 18:49:49 +0100 Subject: [PATCH] add select/unselect all functionality to captured apis table --- .../api-scan/captured-apis/index.hbs | 31 ++++++- .../api-scan/captured-apis/index.scss | 3 + .../api-scan/captured-apis/index.ts | 56 ++++++++++++ .../acceptance/file-details/api-scan-test.js | 88 +++++++++++++++++++ translations/en.json | 1 + translations/ja.json | 1 + 6 files changed, 177 insertions(+), 3 deletions(-) diff --git a/app/components/file-details/api-scan/captured-apis/index.hbs b/app/components/file-details/api-scan/captured-apis/index.hbs index 48e31e2d4..d5e9862ae 100644 --- a/app/components/file-details/api-scan/captured-apis/index.hbs +++ b/app/components/file-details/api-scan/captured-apis/index.hbs @@ -1,4 +1,4 @@ -{{#if this.fetchCapturedApis.isRunning}} +{{#if (and this.fetchCapturedApis.isRunning this.showAPIListLoadingState)}}
@@ -25,13 +25,38 @@ >
{{t 'capturedApiListTitle'}} + + + + + {{t 'selectOrUnSelectAll'}} + + + {{#if this.selectAllCapturedApis.isRunning}} + + {{/if}} + + {{#each pgc.currentPageResults as |ca|}} diff --git a/app/components/file-details/api-scan/captured-apis/index.scss b/app/components/file-details/api-scan/captured-apis/index.scss index 8e892c12f..fe5ec2165 100644 --- a/app/components/file-details/api-scan/captured-apis/index.scss +++ b/app/components/file-details/api-scan/captured-apis/index.scss @@ -5,6 +5,9 @@ .captured-api-title { padding: 1em; + } + + .select-unselect-all-captured-api-cta { background-color: var( --file-details-api-scan-captured-apis-title-background ); diff --git a/app/components/file-details/api-scan/captured-apis/index.ts b/app/components/file-details/api-scan/captured-apis/index.ts index 127c7441d..5d93b6175 100644 --- a/app/components/file-details/api-scan/captured-apis/index.ts +++ b/app/components/file-details/api-scan/captured-apis/index.ts @@ -10,6 +10,7 @@ import type IntlService from 'ember-intl/services/intl'; import type { DS } from 'ember-data'; import ENV from 'irene/config/environment'; +import parseError from 'irene/utils/parse-error'; import type { PaginationProviderActionsArgs } from 'irene/components/ak-pagination-provider'; import type FileModel from 'irene/models/file'; import type CapturedApiModel from 'irene/models/capturedapi'; @@ -41,6 +42,8 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component { const url = [ ENV.endpoints['files'], @@ -151,6 +168,45 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component { + this.showAPIListLoadingState = false; + + try { + await this.ajax.put(this.modAllSelectedAPIsEndpoint, { + data: { is_active: is_active }, + namespace: ENV.namespace_v2, + }); + + await this.fetchCapturedApis.perform(this.limit, this.offset); + await this.getAllAPIsSelectedStatus.perform(); + + this.showAPIListLoadingState = true; + } catch (err) { + const error = err as AdapterError; + const errMsg = this.intl.t('tPleaseTryAgain'); + + this.notify.error(parseError(error, errMsg)); + } + }); + + getAllAPIsSelectedStatus = task(async () => { + try { + const allAPIsSelected = await this.ajax.request<{ is_active: boolean }>( + this.modAllSelectedAPIsEndpoint, + { + namespace: ENV.namespace_v2, + } + ); + + this.allAPIsSelected = allAPIsSelected.is_active; + } catch (err) { + const error = err as AdapterError; + const errMsg = this.intl.t('tPleaseTryAgain'); + + this.notify.error(parseError(error, errMsg)); + } + }); + fetchCapturedApis = task(async (limit: number, offset: number) => { try { this.capturedApiResponse = (await this.store.query('capturedapi', { diff --git a/tests/acceptance/file-details/api-scan-test.js b/tests/acceptance/file-details/api-scan-test.js index cb570dad7..f4d7bbdf2 100644 --- a/tests/acceptance/file-details/api-scan-test.js +++ b/tests/acceptance/file-details/api-scan-test.js @@ -261,6 +261,94 @@ module('Acceptance | file-details/api-scan', function (hooks) { .hasText(t('apiScan')); }); + test.each( + 'it selects and unselects all captured APIs', + [true, false], + async function (assert, is_active) { + // Return opposite toggle states for all API endpoints + this.server.db.capturedapis.forEach((api) => { + this.server.db.capturedapis.update(api.id, { is_active }); + }); + + this.server.get('/v2/files/:id/capturedapis', (schema) => { + const results = schema.capturedapis.all().models; + + return { count: results.length, previous: null, next: null, results }; + }); + + this.server.put('/v2/files/:id/toggle_captured_apis', (schema, req) => { + const { is_active } = JSON.parse(req.requestBody); + + const results = schema.capturedapis.all().models; + + results.forEach((api) => api.update({ is_active })); + + return { count: results.length, previous: null, next: null, results }; + }); + + this.server.get('/v2/files/:id/toggle_captured_apis', () => { + return { is_active }; + }); + + await visit(`/dashboard/file/${this.file.id}/api-scan`); + + assert + .dom('[data-test-fileDetails-apiScan-breadcrumbContainer]') + .exists(); + assert.dom('[data-test-fileDetailsSummary-root]').exists(); + + assert + .dom('[data-test-fileDetails-apiScan-tabs="api-scan-tab"]') + .hasText(t('apiScan')); + + assert + .dom('[data-test-fileDetails-apiScan-capturedApi-title]') + .hasText(t('capturedApiListTitle')); + + assert + .dom('[data-test-fileDetails-apiScan-selectAllCapturedApis-text]') + .hasText(t('selectOrUnSelectAll')); + + let apiEndpoints = findAll( + '[data-test-fileDetails-apiScan-capturedApi-endpointContainer]' + ); + + assert.strictEqual(apiEndpoints.length, 10); + + // All APIs should reflect the correct states + apiEndpoints.forEach((endpoint) => { + const endpointSelector = + '[data-test-fileDetails-apiScan-capturedApi-endpointSelectCheckbox]'; + + if (is_active) { + assert.dom(endpointSelector, endpoint).isNotDisabled().isChecked(); + } else { + assert.dom(endpointSelector, endpoint).isNotDisabled().isNotChecked(); + } + }); + + await click( + '[data-test-fileDetails-apiScan-selectAllCapturedApis-checkbox]' + ); + + apiEndpoints = findAll( + '[data-test-fileDetails-apiScan-capturedApi-endpointContainer]' + ); + + // All APIs should reflect the correct states + apiEndpoints.forEach((endpoint) => { + const endpointSelector = + '[data-test-fileDetails-apiScan-capturedApi-endpointSelectCheckbox]'; + + if (is_active) { + assert.dom(endpointSelector, endpoint).isNotDisabled().isNotChecked(); + } else { + assert.dom(endpointSelector, endpoint).isNotDisabled().isChecked(); + } + }); + } + ); + test('test toggle api endpoint selection', async function (assert) { this.server.get('/v2/files/:id/capturedapis', (schema, req) => { const results = req.queryParams.is_active diff --git a/translations/en.json b/translations/en.json index 2d965a10f..5630bff06 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1403,6 +1403,7 @@ "selectedProject": "Selected Project", "selectedScope": "Selected Scope", "selectedVersion": "Selected OS Version", + "selectOrUnSelectAll": "Select/UnSelect All", "sendInvitation": "Send Invitation", "sendPasswordResetMail": "Send password reset email", "sentInvitationTo": "Sent invitation to", diff --git a/translations/ja.json b/translations/ja.json index a64974cba..d1106a9ed 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -1403,6 +1403,7 @@ "selectedProject": "Selected Project", "selectedScope": "Selected Scope", "selectedVersion": "選択したOSのバージョン", + "selectOrUnSelectAll": "Select/UnSelect All", "sendInvitation": "Send Invitation", "sendPasswordResetMail": "Send password reset email", "sentInvitationTo": "Sent invitation to",