Skip to content

Commit

Permalink
add select/unselect all functionality to captured apis table
Browse files Browse the repository at this point in the history
  • Loading branch information
Elliot Yibaebi committed Jan 23, 2025
1 parent 2e5b844 commit 0bf7183
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 0 deletions.
15 changes: 15 additions & 0 deletions app/components/file-details/api-scan/captured-apis/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@
{{t 'capturedApiListTitle'}}
</AkTypography>

<AkStack @alignItems='center' @spacing='1' class='w-full p-2'>
<AkCheckbox
@checked={{this.allAPIsSelected}}
@onChange={{this.handleSelectAllCapturedApis}}
data-test-fileDetails-apiScan-selectAllCapturedApis-checkbox
/>

<AkTypography
@fontWeight='bold'
data-test-fileDetails-apiScan-selectAllCapturedApis-text
>
{{t 'selectOrUnSelectAll'}}
</AkTypography>
</AkStack>

<AkStack @direction='column' @spacing='1.5'>
{{#each pgc.currentPageResults as |ca|}}
<AkDivider />
Expand Down
51 changes: 51 additions & 0 deletions app/components/file-details/api-scan/captured-apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -41,6 +42,7 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component<F
@service('notifications') declare notify: NotificationService;

@tracked selectedCount = 0;
@tracked allAPIsSelected = false;
@tracked capturedApiResponse: CapturedApiQueryResponse | null = null;
@tracked limit = 10;
@tracked offset = 0;
Expand All @@ -51,6 +53,7 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component<F
) {
super(owner, args);

this.getAllAPIsSelectedStatus.perform();
this.setSelectedApiCount.perform();
this.fetchCapturedApis.perform(this.limit, this.offset);
}
Expand All @@ -67,6 +70,14 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component<F
return this.totalCapturedApiCount === 0;
}

get modAllSelectedAPIsEndpoint() {
return [
ENV.endpoints['files'],
this.args.file.id,
'toggle_captured_apis',
].join('/');
}

setFooterComponentDetails() {
if (this.hasNoCapturedApi) {
return;
Expand Down Expand Up @@ -96,6 +107,11 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component<F
this.fetchCapturedApis.perform(limit, this.offset);
}

@action
handleSelectAllCapturedApis(_: Event, checked: boolean) {
this.selectAllCapturedApis.perform(checked);
}

getSelectedApis = task(async () => {
const url = [
ENV.endpoints['files'],
Expand Down Expand Up @@ -151,6 +167,41 @@ export default class FileDetailsApiScanCapturedApisComponent extends Component<F
}
});

selectAllCapturedApis = task(async (is_active: boolean) => {
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();
} 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', {
Expand Down
84 changes: 84 additions & 0 deletions tests/acceptance/file-details/api-scan-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,90 @@ 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'));

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
Expand Down
1 change: 1 addition & 0 deletions translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 0bf7183

Please sign in to comment.