From 5398103cf821f1d787bcec83103ab4c45da07483 Mon Sep 17 00:00:00 2001 From: yibaebi Date: Thu, 21 Dec 2023 22:31:06 +0100 Subject: [PATCH] complete dast automation scenario UI and integration. --- app/components/ak-svg/no-parameter-data.hbs | 52 ++ app/components/ak-text-field/index.hbs | 1 + app/components/dynamic-scan/modal/index.hbs | 2 +- .../dast-scenario-toggle/index.hbs | 23 + .../dast-scenario-toggle/index.ts | 53 ++ .../dynamicscan-automation-settings/index.hbs | 171 ++---- .../index.scss | 6 + .../dynamicscan-automation-settings/index.ts | 78 +-- .../scenario-table/index.hbs | 60 ++ .../scenario-table/index.scss | 10 + .../scenario-table/index.ts | 57 ++ .../scenario-table/status-header/index.hbs | 29 + .../scenario-table/status-header/index.scss | 6 + .../scenario-table/status/index.hbs | 4 + .../scenario-table/status/index.ts | 12 + .../scenario/index.hbs | 143 +++++ .../scenario/index.scss | 11 + .../scenario/index.ts | 142 +++++ .../scripts-archived/index.hbs | 164 +++++ .../scripts-archived/index.scss | 23 + .../scripts-archived/index.ts | 181 ++++++ .../general-settings/index.hbs | 5 + .../general-settings/index.scss | 5 +- .../project-settings/header/index.hbs | 126 ++-- .../project-settings/header/index.ts | 17 +- .../project-settings/page-wrapper/index.hbs | 3 + .../project-settings/page-wrapper/index.scss | 7 + .../add-parameter-form/index.hbs | 90 +++ .../add-parameter-form/index.scss | 21 + .../view-scenario/add-parameter-form/index.ts | 131 ++++ .../details-column-header/index.hbs | 49 ++ .../details-column-header/index.scss | 16 + .../details-column-header/index.ts | 9 + .../view-scenario/header/index.hbs | 134 ++++ .../view-scenario/header/index.scss | 15 + .../view-scenario/header/index.ts | 74 +++ .../project-settings/view-scenario/index.hbs | 67 ++ .../project-settings/view-scenario/index.scss | 4 + .../project-settings/view-scenario/index.ts | 67 ++ .../view-scenario/parameter-item/index.hbs | 269 ++++++++ .../view-scenario/parameter-item/index.scss | 49 ++ .../view-scenario/parameter-item/index.ts | 171 ++++++ app/router.js | 4 + .../settings/dast-automation-scenario.ts | 27 + app/styles/_component-variables.scss | 35 +- app/styles/_icons.scss | 12 + .../authenticated/project/settings.hbs | 6 +- .../project/settings/analysis.hbs | 2 + .../settings/dast-automation-scenario.hbs | 11 + .../authenticated/project/settings/index.hbs | 2 + mirage/config.js | 51 +- mirage/factories/scan-parameter-group.js | 9 + mirage/factories/scan-parameter.js | 8 + mirage/models/scan-parameter-group.js | 3 + mirage/models/scan-parameter.js | 3 + mirage/scenarios/default.js | 182 +++++- package-lock.json | 69 --- .../components/dynamic-scan-test.js | 2 +- .../dast-scenario-toggle-test.js | 137 +++++ .../index-test.js | 143 +++++ .../scenario-test.js | 572 ++++++++++++++++++ .../scripts-archived-test.js} | 31 +- .../project-settings/header-test.js | 170 +++--- .../view-scenario/add-parameter-form-test.js | 258 ++++++++ .../details-column-header-test.js | 55 ++ .../view-scenario/header-test.js | 258 ++++++++ .../view-scenario/index-test.js | 232 +++++++ .../view-scenario/parameter-item-test.js | 368 +++++++++++ tests/test-utils.js | 14 + .../settings/dast-automation-scenario.js | 17 + translations/en.json | 39 +- translations/ja.json | 38 +- types/ak-svg.d.ts | 1 + 73 files changed, 4859 insertions(+), 457 deletions(-) create mode 100644 app/components/ak-svg/no-parameter-data.hbs create mode 100644 app/components/project-settings/dast-scenario-toggle/index.hbs create mode 100644 app/components/project-settings/dast-scenario-toggle/index.ts create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.hbs create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.scss create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.ts create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.hbs create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.scss create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.hbs create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.ts create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.hbs create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.scss create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.ts create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.hbs create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.scss create mode 100644 app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.ts create mode 100644 app/components/project-settings/page-wrapper/index.hbs create mode 100644 app/components/project-settings/page-wrapper/index.scss create mode 100644 app/components/project-settings/view-scenario/add-parameter-form/index.hbs create mode 100644 app/components/project-settings/view-scenario/add-parameter-form/index.scss create mode 100644 app/components/project-settings/view-scenario/add-parameter-form/index.ts create mode 100644 app/components/project-settings/view-scenario/details-column-header/index.hbs create mode 100644 app/components/project-settings/view-scenario/details-column-header/index.scss create mode 100644 app/components/project-settings/view-scenario/details-column-header/index.ts create mode 100644 app/components/project-settings/view-scenario/header/index.hbs create mode 100644 app/components/project-settings/view-scenario/header/index.scss create mode 100644 app/components/project-settings/view-scenario/header/index.ts create mode 100644 app/components/project-settings/view-scenario/index.hbs create mode 100644 app/components/project-settings/view-scenario/index.scss create mode 100644 app/components/project-settings/view-scenario/index.ts create mode 100644 app/components/project-settings/view-scenario/parameter-item/index.hbs create mode 100644 app/components/project-settings/view-scenario/parameter-item/index.scss create mode 100644 app/components/project-settings/view-scenario/parameter-item/index.ts create mode 100644 app/routes/authenticated/project/settings/dast-automation-scenario.ts create mode 100644 app/templates/authenticated/project/settings/dast-automation-scenario.hbs create mode 100644 mirage/factories/scan-parameter-group.js create mode 100644 mirage/factories/scan-parameter.js create mode 100644 mirage/models/scan-parameter-group.js create mode 100644 mirage/models/scan-parameter.js create mode 100644 tests/integration/components/project-settings/dast-scenario-toggle-test.js create mode 100644 tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js create mode 100644 tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-test.js rename tests/integration/components/project-settings/general-settings/{dynamicscan-automation-settings-test.js => dynamicscan-automation-settings/scripts-archived-test.js} (93%) create mode 100644 tests/integration/components/project-settings/view-scenario/add-parameter-form-test.js create mode 100644 tests/integration/components/project-settings/view-scenario/details-column-header-test.js create mode 100644 tests/integration/components/project-settings/view-scenario/header-test.js create mode 100644 tests/integration/components/project-settings/view-scenario/index-test.js create mode 100644 tests/integration/components/project-settings/view-scenario/parameter-item-test.js create mode 100644 tests/unit/routes/authenticated/project/settings/dast-automation-scenario.js diff --git a/app/components/ak-svg/no-parameter-data.hbs b/app/components/ak-svg/no-parameter-data.hbs new file mode 100644 index 0000000000..a86892bd89 --- /dev/null +++ b/app/components/ak-svg/no-parameter-data.hbs @@ -0,0 +1,52 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/components/ak-text-field/index.hbs b/app/components/ak-text-field/index.hbs index 0b357bdb6f..fbb8ab8b77 100644 --- a/app/components/ak-text-field/index.hbs +++ b/app/components/ak-text-field/index.hbs @@ -54,6 +54,7 @@ diff --git a/app/components/dynamic-scan/modal/index.hbs b/app/components/dynamic-scan/modal/index.hbs index cf5fd37051..d4a2ba9704 100644 --- a/app/components/dynamic-scan/modal/index.hbs +++ b/app/components/dynamic-scan/modal/index.hbs @@ -162,7 +162,7 @@ @variant='body2' data-test-dynamicScanModal-automatedDynamicScanAppiumNote > - {{t 'appiumScriptsNote'}} + {{t 'dynScanAutoSchedNote'}} diff --git a/app/components/project-settings/dast-scenario-toggle/index.hbs b/app/components/project-settings/dast-scenario-toggle/index.hbs new file mode 100644 index 0000000000..62e5e552fe --- /dev/null +++ b/app/components/project-settings/dast-scenario-toggle/index.hbs @@ -0,0 +1,23 @@ + + + + {{#if this.updateScenarioStatus.isRunning}} + + {{/if}} + \ No newline at end of file diff --git a/app/components/project-settings/dast-scenario-toggle/index.ts b/app/components/project-settings/dast-scenario-toggle/index.ts new file mode 100644 index 0000000000..e324f20784 --- /dev/null +++ b/app/components/project-settings/dast-scenario-toggle/index.ts @@ -0,0 +1,53 @@ +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import IntlService from 'ember-intl/services/intl'; + +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import ProjectModel from 'irene/models/project'; +import parseError from 'irene/utils/parse-error'; + +export interface ProjectSettingsDastScenarioStatusToggleSignature { + Args: { + project?: ProjectModel | null; + scenario: ScanParameterGroupModel; + }; +} + +export default class ProjectSettingsDastScenarioStatusToggleComponent extends Component { + @service declare intl: IntlService; + @service('notifications') declare notify: NotificationService; + + get scenario() { + return this.args.scenario; + } + + @action handleToggleClick(event: Event) { + event.stopPropagation(); + } + + @action toggleScenarioStatus(_: Event, checked?: boolean) { + this.updateScenarioStatus.perform(!!checked); + } + + updateScenarioStatus = task(async (checked: boolean) => { + try { + this.scenario.set('isActive', checked); + + const adapterOptions = { projectId: this.args.project?.id }; + await this.scenario.save({ adapterOptions }); + + this.notify.success(this.intl.t('dastAutomation.scenarioStatusUpdated')); + } catch (error) { + this.scenario.set('isActive', !checked); + this.notify.error(parseError(error)); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::DastScenarioToggle': typeof ProjectSettingsDastScenarioStatusToggleComponent; + } +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs index dd00830f44..b9f389b90f 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.hbs @@ -1,6 +1,7 @@ @@ -24,141 +25,45 @@ - - {{t 'appiumScripts'}} - - - - - {{t 'appiumScriptsDescription' htmlSafe=true}} - - - - {{t 'appiumScriptsNote'}} - - + - - - {{#if this.uploadFile.isRunning}} - {{t 'uploading'}}... - {{else}} - {{t 'uploadZipFile'}} - {{/if}} - - + + - {{#if this.automationScripts}} - - {{#each this.automationScripts as |script|}} - + - - - - - - - + + + - - {{script.fileKey}} - - - - - {{t 'uploaded'}} - {{dayjs-from-now script.createdOn}} - - - {{#if (eq script.isValid false)}} - - - - - {{t 'appiumScriptInvalid'}} - - - {{/if}} - - - {{/each}} + + + {{t 'dynScanAutoSchedNote'}} + + - {{/if}} + \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.scss b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.scss index 5ff739e744..4a44fbf5ec 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.scss +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.scss @@ -21,3 +21,9 @@ } } } + +.schedule-automation-toggle { + box-shadow: var( + --project-settings-general-settings-dynamicscan-automation-settings-box-shadow + ); +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts index ef5f13a82c..d64a84938a 100644 --- a/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/index.ts @@ -1,9 +1,5 @@ -// eslint-disable-next-line ember/use-ember-data-rfc-395-imports -import DS from 'ember-data'; - import Component from '@glimmer/component'; import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; import Store from '@ember-data/store'; @@ -11,14 +7,8 @@ import IntlService from 'ember-intl/services/intl'; import ENV from 'irene/config/environment'; import ProjectModel from 'irene/models/project'; -import AutomationScriptModel from 'irene/models/automation-script'; import parseError from 'irene/utils/parse-error'; -type DyanmicscanAutomationSettingsQueryResponse = - DS.AdapterPopulatedRecordArray & { - meta: { count: number }; - }; - export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSignature { Args: { project?: ProjectModel | null; @@ -29,11 +19,9 @@ export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsSign export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsComponent extends Component { @service declare store: Store; @service declare intl: IntlService; - @service ajax: any; + @service declare ajax: any; @service('notifications') declare notify: NotificationService; - @tracked - automationScripts: DyanmicscanAutomationSettingsQueryResponse | null = null; @tracked automationEnabled = false; constructor( @@ -42,7 +30,6 @@ export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettings ) { super(owner, args); - this.getAutomationScripts.perform(); this.getDynamicscanMode.perform(); } @@ -66,16 +53,6 @@ export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettings return this.intl.t('pleaseTryAgain'); } - getAutomationScripts = task(async () => { - try { - this.automationScripts = (await this.store.query('automation-script', { - profileId: this.profileId, - })) as DyanmicscanAutomationSettingsQueryResponse; - } catch (error) { - this.notify.error(parseError(error, this.tPleaseTryAgain)); - } - }); - getDynamicscanMode = task(async () => { try { const dynScanMode = await this.store.queryRecord('dynamicscan-mode', { @@ -109,55 +86,13 @@ export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettings : this.tAppiumScheduledAutomationSuccessOff; this.notify.success(successMsg); - } catch (error) { - this.automationEnabled = !this.automationEnabled; - this.notify.error(parseError(error, this.tSomethingWentWrong)); - } - }); - - uploadFile = task(async (file: any) => { - try { - const urlGetSignedUrl = [ - ENV.endpoints['profiles'], - this.profileId, - ENV.endpoints['uploadAutomationScriptSignedUrl'], - ].join('/'); - - const signedUrlData = { - file_name: file.blob.name, - }; - - const signedUrlResponse = await this.ajax.post(urlGetSignedUrl, { - data: signedUrlData, - }); - - await file.uploadBinary(signedUrlResponse.url, { method: 'PUT' }); - - const urlUploadScript = [ - ENV.endpoints['profiles'], - this.profileId, - ENV.endpoints['uploadAutomationScript'], - ].join('/'); - - const uploadAutomationScriptData = { - file_key: signedUrlResponse.file_key, - file_key_signed: signedUrlResponse.file_key_signed, - }; - - await this.ajax.post(urlUploadScript, { - data: uploadAutomationScriptData, - }); - - this.notify.success(this.intl.t('appiumFileUploadedSuccessfully')); - - this.getAutomationScripts.perform(); } catch (err) { const error = err as AdapterError; - let errMsg = this.intl.t('pleaseTryAgain'); + this.automationEnabled = !this.automationEnabled; if (error.payload) { Object.keys(error.payload).forEach((p) => { - errMsg = error.payload[p]; + let errMsg = error.payload[p]; if (typeof errMsg !== 'string') { errMsg = error.payload[p][0]; @@ -169,14 +104,9 @@ export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettings return; } - this.notify.error(parseError(error, errMsg)); + this.notify.error(parseError(error, this.tSomethingWentWrong)); } }); - - @action - uploadFileWrapper(file: any) { - this.uploadFile.perform(file); - } } declare module '@glint/environment-ember-loose/registry' { diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.hbs new file mode 100644 index 0000000000..a7146a1e71 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.hbs @@ -0,0 +1,60 @@ +{{#if @loadingScenarioList}} + + +{{else if (eq @scenarioList.length 0)}} + + +{{else}} + + + + + {{#if column.headerComponent}} + {{#let (component column.headerComponent) as |Component|}} + + {{/let}} + {{else}} + {{column.name}} + {{/if}} + + + + + + + + {{#if r.columnValue.cellComponent}} + {{#let (component r.columnValue.cellComponent) as |Component|}} + + {{/let}} + {{else}} + {{value}} + {{/if}} + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.scss b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.scss new file mode 100644 index 0000000000..a3b0030152 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.scss @@ -0,0 +1,10 @@ +.dast-scenario-table { + tbody tr { + cursor: pointer; + } + + tbody tr td:last-of-type, + thead tr th:last-of-type { + padding-right: 2em; + } +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.ts new file mode 100644 index 0000000000..d1e58706eb --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/index.ts @@ -0,0 +1,57 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import IntlService from 'ember-intl/services/intl'; + +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import ProjectModel from 'irene/models/project'; + +export interface ProjectSettingsGeneralSettingsDynamicscanAutomationScenarioTableSignature { + Args: { + project?: ProjectModel | null; + scenarioList: ScanParameterGroupModel[]; + loadingScenarioList: boolean; + reloadScenarioList(): void; + }; +} + +export default class ProjectSettingsGeneralSettingsDynamicscanAutomationScenarioTableComponent extends Component { + @service declare router: RouterService; + @service declare intl: IntlService; + + get columns() { + return [ + { + name: this.intl.t('scenario'), + valuePath: 'name', + width: 200, + }, + { + headerComponent: + 'project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header', + cellComponent: + 'project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status', + width: 100, + textAlign: 'right', + }, + ]; + } + + @action onScenarioClick({ + rowValue: scenario, + }: { + rowValue: ScanParameterGroupModel; + }) { + this.router.transitionTo( + 'authenticated.project.settings.dast-automation-scenario', + scenario.id + ); + } +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::GeneralSettings::DynamicscanAutomationSettings::ScenarioTable': typeof ProjectSettingsGeneralSettingsDynamicscanAutomationScenarioTableComponent; + } +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.hbs new file mode 100644 index 0000000000..7e451853f4 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.hbs @@ -0,0 +1,29 @@ + + {{t 'status'}} + + + <:tooltipContent> +
+ + {{t 'dastAutomation.statusColumnToggleInfo'}} + +
+ + + <:default> + + +
+
\ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.scss b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.scss new file mode 100644 index 0000000000..c00e337805 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status-header/index.scss @@ -0,0 +1,6 @@ +.tooltip-content { + width: 264px; + padding: 0.5em; + box-sizing: border-box; + white-space: normal; +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.hbs new file mode 100644 index 0000000000..ee78cd7dc2 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.hbs @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.ts new file mode 100644 index 0000000000..7ff70d6739 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-table/status/index.ts @@ -0,0 +1,12 @@ +import Component from '@glimmer/component'; +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import ProjectModel from 'irene/models/project'; + +export interface ProjectSettingsGeneralSettingsDynamicscanAutomationScenarioTableStatusSignature { + Args: { + project?: ProjectModel; + scenario: ScanParameterGroupModel; + }; +} + +export default class ProjectSettingsGeneralSettingsDynamicscanAutomationScenarioTableStatusComponent extends Component {} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.hbs new file mode 100644 index 0000000000..2d11aade92 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.hbs @@ -0,0 +1,143 @@ + + + + {{t 'dastAutomation.automationScenarios'}} + + + + {{t 'dastAutomation.scenarioListDesc'}} + + + + + + <:leftIcon> + + + + <:default>{{t 'dastAutomation.addScenario'}} + + + + + +{{#if this.showAddScenarioModal}} + + <:default> + + + + {{t 'scenario'}} + + + + + + + + {{t 'status'}} + + + + + + + + <:footer> + + + + + {{t 'cancel'}} + + + + {{t 'add'}} + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.scss b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.scss new file mode 100644 index 0000000000..11f8f5cf2c --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.scss @@ -0,0 +1,11 @@ +.scenario-name-form-control { + background-color: var( + --project-settings-general-settings-dynamicscan-automation-scenario-textfield-bg + ); +} + +.scenario-form-footer-container { + box-shadow: var( + --project-settings-general-settings-dynamicscan-automation-scenario-form-footer-box-shadow + ); +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.ts new file mode 100644 index 0000000000..eff7585e5d --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scenario/index.ts @@ -0,0 +1,142 @@ +/* eslint-disable ember/use-ember-data-rfc-395-imports */ +import DS from 'ember-data'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; +import IntlService from 'ember-intl/services/intl'; + +import ProjectModel from 'irene/models/project'; +import ENV from 'irene/config/environment'; +import parseError from 'irene/utils/parse-error'; +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; + +import styles from './index.scss'; + +type ProjectSceanriosArrayResponse = + DS.AdapterPopulatedRecordArray; + +export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScenarioSignature { + Args: { + project?: ProjectModel | null; + profileId?: string | number; + }; +} + +export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScenarioComponent extends Component { + @service declare store: Store; + @service declare intl: IntlService; + @service('notifications') declare notify: NotificationService; + + @tracked showAddScenarioModal = false; + @tracked scenarioName = ''; + @tracked scenarioStatus = false; + @tracked projectScenarios: ProjectSceanriosArrayResponse | null = null; + + namespace = ENV.namespace_v2; + + constructor( + owner: unknown, + args: ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScenarioSignature['Args'] + ) { + super(owner, args); + + this.fetchProjectScenarios.perform(); + } + + get scenarioNameFormControlClass() { + return styles['scenario-name-form-control']; + } + + get disableScenarioAddBtn() { + return !this.scenarioName; + } + + get scenarioList() { + return this.projectScenarios?.toArray() || []; + } + + @action reloadProjectScenarios() { + this.fetchProjectScenarios.perform(); + } + + @action createScenario() { + this.addScenarioToProject.perform(); + } + + @action closeAddScenarioModal() { + this.showAddScenarioModal = false; + } + + @action toggleScenarioStatus(_: Event, checked?: boolean) { + this.scenarioStatus = !!checked; + } + + @action openAddScenarioModal() { + this.showAddScenarioModal = true; + } + + @action + handleScenarioNameChange(event: Event) { + this.scenarioName = (event.target as HTMLInputElement).value; + } + + addScenarioToProject = task(async () => { + if (!this.scenarioName) { + this.notify.error(this.intl.t('dastAutomation.enterScenarioName')); + + return; + } + + try { + const scenario = this.store.createRecord('scan-parameter-group', { + name: this.scenarioName, + description: '', + }); + + const adapterOptions = { projectId: this.args.project?.id }; + await scenario.save({ adapterOptions }); + + if (this.scenarioStatus) { + scenario.set('isActive', this.scenarioStatus); + await scenario.save({ adapterOptions }); + } + + this.notify.success(this.intl.t('dastAutomation.scenarioAdded')); + + this.scenarioName = ''; + this.scenarioStatus = false; + + this.closeAddScenarioModal(); + this.reloadProjectScenarios(); + } catch (error) { + const errors = error as any; + + if (errors?.errors?.[0]?.source?.pointer === '/data/attributes/name') { + this.notify.error(errors?.errors?.[0]?.detail); + + return; + } + + this.notify.error(parseError(error)); + } + }); + + fetchProjectScenarios = task(async () => { + try { + this.projectScenarios = (await this.store.query('scan-parameter-group', { + projectId: this.args.project?.id, + })) as ProjectSceanriosArrayResponse; + } catch (error) { + this.notify.error(parseError(error)); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::GeneralSettings::DynamicscanAutomationSettings::Scenario': typeof ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScenarioComponent; + } +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.hbs b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.hbs new file mode 100644 index 0000000000..f2cdc37fdb --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.hbs @@ -0,0 +1,164 @@ + + + + {{t 'dynamicScanAutomation'}} + + + + <:icon> + + + + + + + {{t 'appiumScripts'}} + + + + + {{t 'appiumScriptsDescription' htmlSafe=true}} + + + + {{t 'appiumScriptsSchedNote'}} + + + + + + {{#if this.uploadFile.isRunning}} + {{t 'uploading'}}... + {{else}} + {{t 'uploadZipFile'}} + {{/if}} + + + + {{#if this.automationScripts}} + + {{#each this.automationScripts as |script|}} + + + + + + + + + + + {{script.fileKey}} + + + + + {{t 'uploaded'}} + {{dayjs-from-now script.createdOn}} + + + {{#if (eq script.isValid false)}} + + + + + {{t 'appiumScriptInvalid'}} + + + {{/if}} + + + {{/each}} + + {{/if}} + \ No newline at end of file diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.scss b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.scss new file mode 100644 index 0000000000..5ff739e744 --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.scss @@ -0,0 +1,23 @@ +.script-details-container { + box-shadow: var( + --project-settings-general-settings-dynamicscan-automation-settings-box-shadow + ); + border-radius: var( + --project-settings-general-settings-dynamicscan-automation-settings-border-radius + ); + border: 1px solid + var( + --project-settings-general-settings-dynamicscan-automation-settings-border-color + ); + + .script-file-details { + border-top: 1px solid + var( + --project-settings-general-settings-dynamicscan-automation-settings-border-color + ); + + .text-with-icon { + gap: 0.2587em; + } + } +} diff --git a/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.ts b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.ts new file mode 100644 index 0000000000..c78db7768b --- /dev/null +++ b/app/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived/index.ts @@ -0,0 +1,181 @@ +// eslint-disable-next-line ember/use-ember-data-rfc-395-imports +import DS from 'ember-data'; + +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; +import IntlService from 'ember-intl/services/intl'; + +import ENV from 'irene/config/environment'; +import ProjectModel from 'irene/models/project'; +import AutomationScriptModel from 'irene/models/automation-script'; +import parseError from 'irene/utils/parse-error'; + +type DyanmicscanAutomationSettingsQueryResponse = + DS.AdapterPopulatedRecordArray & { + meta: { count: number }; + }; + +export interface ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScriptsSignature { + Args: { + project?: ProjectModel | null; + profileId?: string | number; + }; +} + +export default class ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScriptsComponent extends Component { + @service declare store: Store; + @service declare intl: IntlService; + @service declare ajax: any; + @service('notifications') declare notify: NotificationService; + + @tracked + automationScripts: DyanmicscanAutomationSettingsQueryResponse | null = null; + + @tracked automationEnabled = false; + + constructor( + owner: unknown, + args: ProjectSettingsGeneralSettingsDyanmicscanAutomationSettingsScriptsSignature['Args'] + ) { + super(owner, args); + + this.getAutomationScripts.perform(); + this.getDynamicscanMode.perform(); + } + + get profileId() { + return this.args.profileId; + } + + get tAppiumScheduledAutomationSuccessOn() { + return this.intl.t('appiumScheduledAutomationSuccessOn'); + } + + get tAppiumScheduledAutomationSuccessOff() { + return this.intl.t('appiumScheduledAutomationSuccessOff'); + } + + get tSomethingWentWrong() { + return this.intl.t('somethingWentWrong'); + } + + get tPleaseTryAgain() { + return this.intl.t('pleaseTryAgain'); + } + + getAutomationScripts = task(async () => { + try { + this.automationScripts = (await this.store.query('automation-script', { + profileId: this.profileId, + })) as DyanmicscanAutomationSettingsQueryResponse; + } catch (error) { + this.notify.error(parseError(error, this.tPleaseTryAgain)); + } + }); + + getDynamicscanMode = task(async () => { + try { + const dynScanMode = await this.store.queryRecord('dynamicscan-mode', { + id: this.profileId, + }); + + this.automationEnabled = dynScanMode.dynamicscanMode === 'Automated'; + } catch (error) { + this.notify.error(parseError(error, this.tPleaseTryAgain)); + } + }); + + toggleDynamicscanMode = task(async () => { + try { + this.automationEnabled = !this.automationEnabled; + + const dynamicscanMode = [ + ENV.endpoints['profiles'], + this.profileId, + ENV.endpoints['dynamicscanMode'], + ].join('/'); + + const data = { + dynamicscan_mode: this.automationEnabled ? 'Automated' : 'Manual', + }; + + await this.ajax.put(dynamicscanMode, { data }); + + const successMsg = this.automationEnabled + ? this.tAppiumScheduledAutomationSuccessOn + : this.tAppiumScheduledAutomationSuccessOff; + + this.notify.success(successMsg); + } catch (error) { + this.automationEnabled = !this.automationEnabled; + this.notify.error(parseError(error, this.tSomethingWentWrong)); + } + }); + + uploadFile = task(async (file: any) => { + try { + const urlGetSignedUrl = [ + ENV.endpoints['profiles'], + this.profileId, + ENV.endpoints['uploadAutomationScriptSignedUrl'], + ].join('/'); + + const signedUrlData = { + file_name: file.blob.name, + }; + + const signedUrlResponse = await this.ajax.post(urlGetSignedUrl, { + data: signedUrlData, + }); + + await file.uploadBinary(signedUrlResponse.url, { method: 'PUT' }); + + const urlUploadScript = [ + ENV.endpoints['profiles'], + this.profileId, + ENV.endpoints['uploadAutomationScript'], + ].join('/'); + + const uploadAutomationScriptData = { + file_key: signedUrlResponse.file_key, + file_key_signed: signedUrlResponse.file_key_signed, + }; + + await this.ajax.post(urlUploadScript, { + data: uploadAutomationScriptData, + }); + + this.notify.success(this.intl.t('appiumFileUploadedSuccessfully')); + + this.getAutomationScripts.perform(); + } catch (err) { + const error = err as AdapterError; + let errMsg = this.intl.t('pleaseTryAgain'); + + if (error.payload) { + Object.keys(error.payload).forEach((p) => { + errMsg = error.payload[p]; + + if (typeof errMsg !== 'string') { + errMsg = error.payload[p][0]; + } + + this.notify.error(errMsg); + }); + + return; + } + + this.notify.error(parseError(error, errMsg)); + } + }); + + @action + uploadFileWrapper(file: any) { + this.uploadFile.perform(file); + } +} diff --git a/app/components/project-settings/general-settings/index.hbs b/app/components/project-settings/general-settings/index.hbs index 6fbe1c5ade..350dd8020e 100644 --- a/app/components/project-settings/general-settings/index.hbs +++ b/app/components/project-settings/general-settings/index.hbs @@ -87,6 +87,11 @@ {{#if this.organization.selected.features.dynamicscan_automation}} + {{!-- --}} + -
- - {{t 'settings'}} - + {{#unless @isDASTScenarioPage}} +
+ + {{t 'settings'}} + - - {{t 'projectSettings.headerText'}} - -
+ + {{t 'projectSettings.headerText'}} + +
- -
- -
- - - {{this.project.packageName}} - +
+ +
- - {{t 'projectID'}} - - - {{this.project.id}} - + + {{this.project.packageName}} + + + + {{t 'projectID'}} + - + {{this.project.id}} + +
-
- + -
+ - - {{#each this.tabItems as |item|}} - - <:default> - {{item.label}} - - - {{/each}} - + + {{#each this.tabItems as |item|}} + + <:default> + {{item.label}} + + + {{/each}} + + {{/unless}} \ No newline at end of file diff --git a/app/components/project-settings/header/index.ts b/app/components/project-settings/header/index.ts index f885f18b21..70d38820f9 100644 --- a/app/components/project-settings/header/index.ts +++ b/app/components/project-settings/header/index.ts @@ -6,6 +6,7 @@ import ProjectModel from 'irene/models/project'; interface ProjectSettingsHeaderSignature { Args: { project: ProjectModel | null; + isDASTScenarioPage?: boolean; }; } @@ -27,11 +28,17 @@ export default class ProjectSettingsHeaderComponent extends Component + {{yield}} + \ No newline at end of file diff --git a/app/components/project-settings/page-wrapper/index.scss b/app/components/project-settings/page-wrapper/index.scss new file mode 100644 index 0000000000..1a72b22f6c --- /dev/null +++ b/app/components/project-settings/page-wrapper/index.scss @@ -0,0 +1,7 @@ +.project-settings-page-wrapper { + padding: 1.4286em; + padding-top: 0em; + margin: -0.7143em -0.5em; + background-color: var(--project-settings-page-wrapper-background-color); + min-height: calc(100vh - 56px); +} diff --git a/app/components/project-settings/view-scenario/add-parameter-form/index.hbs b/app/components/project-settings/view-scenario/add-parameter-form/index.hbs new file mode 100644 index 0000000000..6f09810930 --- /dev/null +++ b/app/components/project-settings/view-scenario/add-parameter-form/index.hbs @@ -0,0 +1,90 @@ +
+ + <:rightAdornment> + {{#if this.parameterInputTypeError}} + + <:tooltipContent> +
+ + {{t + 'dastAutomation.paramTypeDupText' + htmlSafe=true + type=this.changeset.parameterType + }} + +
+ + + <:default> + + +
+ + {{else if this.changeset.parameterType}} + + + + {{/if}} + +
+ + + <:rightAdornment> + {{#if this.changeset.parameterValue}} + + + + {{/if}} + + + + + {{t 'add'}} + +
\ No newline at end of file diff --git a/app/components/project-settings/view-scenario/add-parameter-form/index.scss b/app/components/project-settings/view-scenario/add-parameter-form/index.scss new file mode 100644 index 0000000000..b639e25415 --- /dev/null +++ b/app/components/project-settings/view-scenario/add-parameter-form/index.scss @@ -0,0 +1,21 @@ +.scenario-add-form-container { + width: 100%; + display: grid; + grid-template-columns: 260px 260px 80px; + column-gap: 2.1428em; + align-content: center; + min-width: 800px; + border: 1px solid var(--project-settings-view-scenario-border-light); + background: var(--project-settings-view-scenario-background-dark); + margin-bottom: 1.4286em; + + .add-parameter-text-field-form-root { + background-color: var(--project-settings-view-scenario-background-white); + } + + .tooltip-content { + width: 264px; + padding: 0.5em; + box-sizing: border-box; + } +} diff --git a/app/components/project-settings/view-scenario/add-parameter-form/index.ts b/app/components/project-settings/view-scenario/add-parameter-form/index.ts new file mode 100644 index 0000000000..bc0f01850f --- /dev/null +++ b/app/components/project-settings/view-scenario/add-parameter-form/index.ts @@ -0,0 +1,131 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import IntlService from 'ember-intl/services/intl'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; + +import lookupValidator from 'ember-changeset-validations'; +import { Changeset } from 'ember-changeset'; +import { BufferedChangeset } from 'ember-changeset/types'; +import { validatePresence } from 'ember-changeset-validations/validators'; + +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import parseError from 'irene/utils/parse-error'; + +import styles from './index.scss'; + +type ChangesetBufferProps = Partial & { + parameterValue: string; + parameterType: string; +}; + +const ChangeValidator = { + parameterValue: [validatePresence(true)], + parameterType: [validatePresence(true)], +}; + +interface ProjectSettingsViewScenarioAddParameterSignature { + Args: { + scenario: ScanParameterGroupModel; + reloadParameterList(): void; + }; +} + +export default class ProjectSettingsViewScenarioAddParameterComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + @tracked changeset: ChangesetBufferProps | null = null; + + model = {}; + + constructor( + owner: unknown, + args: ProjectSettingsViewScenarioAddParameterSignature['Args'] + ) { + super(owner, args); + + this.changeset = Changeset( + this.model, + lookupValidator(ChangeValidator), + ChangeValidator + ) as ChangesetBufferProps; + } + + get scenario() { + return this.args.scenario; + } + + get addScenarioTextFieldClassess() { + return { + root: styles['add-parameter-text-field-form-root'], + }; + } + + get disableAddBtn() { + return !this.changeset?.parameterType || !this.changeset.parameterValue; + } + + get parameterInputTypeError() { + return this.changeset?.error?.['parameterType']?.validation as string; + } + + get parameterInputTypeHasError() { + return !!this.parameterInputTypeError; + } + + @action handleClearParameterValue() { + this.changeset?.set?.('parameterValue', ''); + } + + @action handleClearParameterType() { + this.changeset?.set?.('parameterType', ''); + } + + createScanParameter = task(async () => { + await this.changeset?.validate?.(); + + if (!this.changeset?.isValid) { + return; + } + + try { + const parameter = this.store.createRecord('scan-parameter', { + name: this.changeset.parameterType, + value: this.changeset.parameterValue, + }); + + const adapterOptions = { scenarioId: this.scenario.id }; + await parameter.save({ adapterOptions }); + + this.changeset?.set?.('parameterValue', ''); + this.changeset?.set?.('parameterType', ''); + this.changeset?.rollbackProperty?.('parameterType'); // To clear error of the parameter type + + this.notify.success(this.intl.t('dastAutomation.parameterAdded')); + this.args.reloadParameterList(); + } catch (err) { + const errors = err as any; + + if (errors?.errors?.[0]?.source?.pointer === '/data/attributes/name') { + this.changeset?.addError?.( + 'parameterType', + errors?.errors?.[0]?.detail + ); + + return; + } + + this.notify.error(parseError(errors, this.intl.t('tSomethingWentWrong'))); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::ViewScenario::AddParameterForm': typeof ProjectSettingsViewScenarioAddParameterComponent; + } +} diff --git a/app/components/project-settings/view-scenario/details-column-header/index.hbs b/app/components/project-settings/view-scenario/details-column-header/index.hbs new file mode 100644 index 0000000000..8664e1f75b --- /dev/null +++ b/app/components/project-settings/view-scenario/details-column-header/index.hbs @@ -0,0 +1,49 @@ +
+ + + + + <:tooltipContent> +
+ + {{t 'dastAutomation.inputTypeColumnHeaderInfo' htmlSafe=true}} + +
+ + + <:default> + + +
+
+ + +
\ No newline at end of file diff --git a/app/components/project-settings/view-scenario/details-column-header/index.scss b/app/components/project-settings/view-scenario/details-column-header/index.scss new file mode 100644 index 0000000000..c7efb348cd --- /dev/null +++ b/app/components/project-settings/view-scenario/details-column-header/index.scss @@ -0,0 +1,16 @@ +.scenario-details-columns-container { + width: 100%; + display: grid; + grid-template-columns: 260px 260px 80px; + column-gap: 2.1428em; + align-content: center; + min-width: 800px; + padding-top: 0.7857em; + padding-bottom: 0.7857em; + + .tooltip-content { + width: 264px; + padding: 0.5em; + box-sizing: border-box; + } +} diff --git a/app/components/project-settings/view-scenario/details-column-header/index.ts b/app/components/project-settings/view-scenario/details-column-header/index.ts new file mode 100644 index 0000000000..8645dbe75e --- /dev/null +++ b/app/components/project-settings/view-scenario/details-column-header/index.ts @@ -0,0 +1,9 @@ +import Component from '@glimmer/component'; + +export default class ProjectSettingsViewScenarioDetailsColumnHeaderComponent extends Component {} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::ViewScenario::DetailsColumnHeader': typeof ProjectSettingsViewScenarioDetailsColumnHeaderComponent; + } +} diff --git a/app/components/project-settings/view-scenario/header/index.hbs b/app/components/project-settings/view-scenario/header/index.hbs new file mode 100644 index 0000000000..f6db13d01e --- /dev/null +++ b/app/components/project-settings/view-scenario/header/index.hbs @@ -0,0 +1,134 @@ + + + + + {{t 'scenario'}} + + + + {{@scenario.name}} + + + + + + {{t 'enabled'}} + + + + + + + + + + + + + +{{#if this.showDeleteScenarioModal}} + + <:default> + + + {{t + 'dastAutomation.scenarioDeleteConfirm' + htmlSafe=true + scenarioName=@scenario.name + }} + + + + + <:footer> + + + + + {{t 'cancel'}} + + + + {{t 'yesDelete'}} + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/project-settings/view-scenario/header/index.scss b/app/components/project-settings/view-scenario/header/index.scss new file mode 100644 index 0000000000..c202cbba23 --- /dev/null +++ b/app/components/project-settings/view-scenario/header/index.scss @@ -0,0 +1,15 @@ +.view-scenario-header-root { + background-color: var(--project-settings-view-scenario-background-white); +} + +.view-scenario-header-root { + border: 1px solid var(--project-settings-view-scenario-header-border-color); + border-radius: var(--project-settings-view-scenario-header-border-radius); + box-shadow: var( + --project-settings-view-scenario-header-scenario-details-box-shadow + ); +} + +.scenario-delete-footer-container { + box-shadow: var(--project-settings-view-scenario-modal-footer-box-shadow); +} diff --git a/app/components/project-settings/view-scenario/header/index.ts b/app/components/project-settings/view-scenario/header/index.ts new file mode 100644 index 0000000000..248456cb18 --- /dev/null +++ b/app/components/project-settings/view-scenario/header/index.ts @@ -0,0 +1,74 @@ +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import { task } from 'ember-concurrency'; +import IntlService from 'ember-intl/services/intl'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +import ProjectModel from 'irene/models/project'; +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import parseError from 'irene/utils/parse-error'; + +interface ProjectSettingsViewScenarioHeaderSignature { + Args: { + project: ProjectModel | null; + scenario: ScanParameterGroupModel; + }; +} + +export default class ProjectSettingsViewScenarioHeaderComponent extends Component { + @service declare intl: IntlService; + @service declare router: RouterService; + @service('notifications') declare notify: NotificationService; + + @tracked showDeleteScenarioModal = false; + + get scenario() { + return this.args.scenario; + } + + get isNotDefaultScenario() { + return !this.scenario.isDefault; + } + + @action handleDeleteScenario() { + this.showDeleteScenarioModal = true; + } + + @action hideDeleteScenarioModal() { + this.showDeleteScenarioModal = false; + } + + @action deleteScenario() { + this.deleteProjectScenario.perform(); + } + + deleteProjectScenario = task(async () => { + try { + const adapterOptions = { projectId: this.args.project?.id }; + + await this.scenario.destroyRecord({ adapterOptions }); + this.scenario.unloadRecord(); + + this.notify.success( + this.intl.t('dastAutomation.scenarioDeleted', { + scenarioName: this.scenario.name, + }) + ); + + this.router.transitionTo( + 'authenticated.project.settings', + String(this.args.project?.id) + ); + } catch (err) { + this.notify.error(parseError(err, this.intl.t('tSomethingWentWrong'))); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::ViewScenario::Header': typeof ProjectSettingsViewScenarioHeaderComponent; + } +} diff --git a/app/components/project-settings/view-scenario/index.hbs b/app/components/project-settings/view-scenario/index.hbs new file mode 100644 index 0000000000..a40436d1eb --- /dev/null +++ b/app/components/project-settings/view-scenario/index.hbs @@ -0,0 +1,67 @@ + + + + + + + + + {{#if this.fetchScanParameters.isRunning}} + + + {{else if (eq this.parameterList.length 0)}} + + + + + + {{t 'noDataAvailable'}} + + + + {{t 'dastAutomation.noParamaterAvailable'}} + + + + + {{else}} + + {{#each this.parameterList as |pm|}} + + {{/each}} + + {{/if}} + + \ No newline at end of file diff --git a/app/components/project-settings/view-scenario/index.scss b/app/components/project-settings/view-scenario/index.scss new file mode 100644 index 0000000000..a62dfddc96 --- /dev/null +++ b/app/components/project-settings/view-scenario/index.scss @@ -0,0 +1,4 @@ +.empty-secanario-container { + padding: 5.7143em 3.7143em; + background-color: var(--project-settings-view-scenario-background-white); +} diff --git a/app/components/project-settings/view-scenario/index.ts b/app/components/project-settings/view-scenario/index.ts new file mode 100644 index 0000000000..2cde922a45 --- /dev/null +++ b/app/components/project-settings/view-scenario/index.ts @@ -0,0 +1,67 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import IntlService from 'ember-intl/services/intl'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; +import RouterService from '@ember/routing/router-service'; + +import ProjectModel from 'irene/models/project'; +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import parseError from 'irene/utils/parse-error'; +import ScanParameterModel from 'irene/models/scan-parameter'; + +interface ProjectSettingsViewScenarioSignature { + Args: { + project: ProjectModel | null; + scenario: ScanParameterGroupModel; + }; +} + +export default class ProjectSettingsViewScenarioComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service declare router: RouterService; + @service('notifications') declare notify: NotificationService; + @service declare ajax: any; + + @tracked parameterList: ScanParameterModel[] = []; + + model = {}; + + constructor( + owner: unknown, + args: ProjectSettingsViewScenarioSignature['Args'] + ) { + super(owner, args); + + this.fetchScanParameters.perform(); + } + + get scenario() { + return this.args.scenario; + } + + @action reloadParameterList() { + this.fetchScanParameters.perform(); + } + + fetchScanParameters = task(async () => { + try { + const parameterList = await this.store.query('scan-parameter', { + groupId: this.args.scenario?.id, + }); + + this.parameterList = parameterList.toArray(); + } catch (error) { + this.notify.error(parseError(error)); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::ViewScenario': typeof ProjectSettingsViewScenarioComponent; + } +} diff --git a/app/components/project-settings/view-scenario/parameter-item/index.hbs b/app/components/project-settings/view-scenario/parameter-item/index.hbs new file mode 100644 index 0000000000..8292b0c224 --- /dev/null +++ b/app/components/project-settings/view-scenario/parameter-item/index.hbs @@ -0,0 +1,269 @@ + + + {{@parameter.name}} + + + + + <:rightAdornment> + {{#if this.editParameter.isRunning}} + + + {{else}} + + + + {{/if}} + + + + + + {{#if this.isEditing}} + + + + + + + + {{/if}} + + + + + + + + + +{{#if this.showDeleteScenarioParameter}} + + <:default> + + + {{t + 'dastAutomation.parameterDeleteConfirm' + htmlSafe=true + scenarioName=@scenario.name + }} + + + + + + {{t 'dastAutomation.inputType'}} + + + + {{@parameter.name}} + + + + + + {{t 'dastAutomation.inputValue'}} + + + + {{@parameter.value}} + + + + + + + <:footer> + + + + + {{t 'cancel'}} + + + + {{t 'yesDelete'}} + + + + +{{/if}} + +{{#if this.showMaskSecureFieldModal}} + + <:default> + + + {{t 'dastAutomation.maskParameterText'}} + + + + + <:footer> + + + + + {{t 'cancel'}} + + + + {{t 'yesSecure'}} + + + + +{{/if}} \ No newline at end of file diff --git a/app/components/project-settings/view-scenario/parameter-item/index.scss b/app/components/project-settings/view-scenario/parameter-item/index.scss new file mode 100644 index 0000000000..97462445d5 --- /dev/null +++ b/app/components/project-settings/view-scenario/parameter-item/index.scss @@ -0,0 +1,49 @@ +.scenario-details-group { + width: 100%; + column-gap: 2.1428em; + display: grid; + grid-template-columns: 260px 260px 80px 1fr; + align-content: center; + min-width: 800px; + min-height: 60px; + background-color: var(--project-settings-view-scenario-background-white); + border-bottom: 1px solid + var(--project-settings-view-scenario-parameter-item-border-color); + + .details-delete-btn, + .secure-icon.hide { + display: none; + } + + &:hover .details-delete-btn, + &:hover .secure-icon.hide, + .secure-icon.show { + display: flex; + } + + .parameter-value-text-field-form-control { + background-color: var( + --project-settings-view-scenario-parameter-text-field-background + ); + } + + .parameter-value-text-field-root { + cursor: text; + background: transparent; + } + + .parameter-value-text-field { + & ~ :global(.ak-text-input-outlined) { + border: 0px solid var(--project-settings-view-scenario-border-light) !important; + } + + &:active ~ :global(.ak-text-input-outlined), + &:focus ~ :global(.ak-text-input-outlined) { + border-width: 2px !important; + } + } +} + +.parameter-modal-footer-container { + box-shadow: var(--project-settings-view-scenario-modal-footer-box-shadow); +} diff --git a/app/components/project-settings/view-scenario/parameter-item/index.ts b/app/components/project-settings/view-scenario/parameter-item/index.ts new file mode 100644 index 0000000000..90703b9ce2 --- /dev/null +++ b/app/components/project-settings/view-scenario/parameter-item/index.ts @@ -0,0 +1,171 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import IntlService from 'ember-intl/services/intl'; +import { tracked } from '@glimmer/tracking'; +import { action } from '@ember/object'; +import { task } from 'ember-concurrency'; +import Store from '@ember-data/store'; + +import ScanParameterGroupModel from 'irene/models/scan-parameter-group'; +import parseError from 'irene/utils/parse-error'; +import ScanParameterModel from 'irene/models/scan-parameter'; + +import styles from './index.scss'; + +interface ProjectSettingsViewScenarioParameterItemSignature { + Args: { + parameter: ScanParameterModel; + scenario: ScanParameterGroupModel; + reloadParameterList(): void; + }; +} + +export default class ProjectSettingsViewScenarioParameterItemComponent extends Component { + @service declare intl: IntlService; + @service declare store: Store; + @service('notifications') declare notify: NotificationService; + + @tracked isEditing = false; + @tracked parameterValueEditText = ''; + @tracked toggleShowSecureField = false; + @tracked showDeleteScenarioParameter = false; + @tracked showMaskSecureFieldModal = false; + + constructor( + owner: unknown, + args: ProjectSettingsViewScenarioParameterItemSignature['Args'] + ) { + super(owner, args); + + this.parameterValueEditText = this.args.parameter.value; + } + + get textFieldClasses() { + return { + formControl: styles['parameter-value-text-field-form-control'], + root: styles['parameter-value-text-field-root'], + }; + } + + get scenario() { + return this.args.scenario; + } + + get parameter() { + return this.args.parameter; + } + + get isNotSecure() { + return !this.parameter.isSecure; + } + + get hideParameterValueText() { + return !this.isEditing && this.parameter.isSecure; + } + + @action handleParamFieldEditing() { + this.isEditing = true; + + if (this.parameter.isSecure) { + this.parameterValueEditText = ''; + } + } + + @action handleParameterEditDone() { + const valueIsEmpty = this.parameterValueEditText === ''; + const valueIsUnchanged = + this.parameterValueEditText === this.parameter.value; + + if (valueIsUnchanged || valueIsEmpty) { + this.isEditing = false; + } + + if (valueIsEmpty) { + this.parameterValueEditText = this.parameter.value; + } + } + + @action handleParamFieldChange(event: Event) { + this.parameterValueEditText = (event.target as HTMLInputElement).value; + } + + @action showDeleteParameterModal() { + this.showDeleteScenarioParameter = true; + } + + @action hideDeleteParameterModal() { + this.showDeleteScenarioParameter = false; + } + + @action showMaskParameterValueModal() { + if (this.parameter.isSecure) { + return; + } + + this.showMaskSecureFieldModal = true; + } + + @action closeMaskParameterValueModal() { + this.showMaskSecureFieldModal = false; + } + + @action cancelParameterValueEdit() { + this.parameterValueEditText = this.parameter.value; + this.isEditing = false; + } + + @action saveParameterValueEdit() { + this.editParameter.perform( + this.parameterValueEditText, + this.parameter.isSecure + ); + } + + @action secureParameterValue() { + this.editParameter.perform(this.parameter.value, true); + } + + @action deleteParameter() { + this.deleteScenarioParameter.perform(); + } + + editParameter = task(async (value: string, isSecure: boolean) => { + try { + this.parameter.setProperties({ + value, + isSecure, + }); + + const adapterOptions = { scenarioId: this.scenario.id }; + await this.parameter.save({ adapterOptions }); + + this.isEditing = false; + + this.closeMaskParameterValueModal(); + this.notify.success(this.intl.t('dastAutomation.paramEditSuccess')); + this.args.reloadParameterList(); + } catch (err) { + this.notify.error(parseError(err, this.intl.t('tSomethingWentWrong'))); + } + }); + + deleteScenarioParameter = task(async () => { + try { + const adapterOptions = { scenarioId: this.scenario.id }; + await this.parameter.destroyRecord({ adapterOptions }); + this.parameter.unloadRecord(); + + this.hideDeleteParameterModal(); + this.notify.success(this.intl.t('dastAutomation.paramDeleteSuccess')); + this.args.reloadParameterList(); + } catch (err) { + this.notify.error(parseError(err, this.intl.t('tSomethingWentWrong'))); + } + }); +} + +declare module '@glint/environment-ember-loose/registry' { + export default interface Registry { + 'ProjectSettings::ViewScenario::ParameterItem': typeof ProjectSettingsViewScenarioParameterItemComponent; + } +} diff --git a/app/router.js b/app/router.js index 4de5612115..4faaf21be6 100644 --- a/app/router.js +++ b/app/router.js @@ -55,6 +55,10 @@ Router.map(function () { function () { this.route('settings', function () { this.route('analysis'); + + this.route('dast-automation-scenario', { + path: '/dast-automation-scenario/:scenario_id', + }); }); this.route('files'); } diff --git a/app/routes/authenticated/project/settings/dast-automation-scenario.ts b/app/routes/authenticated/project/settings/dast-automation-scenario.ts new file mode 100644 index 0000000000..79d8dcd970 --- /dev/null +++ b/app/routes/authenticated/project/settings/dast-automation-scenario.ts @@ -0,0 +1,27 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import ProjectModel from 'irene/models/project'; +import { ScrollToTop } from 'irene/utils/scroll-to-top'; +import Store from '@ember-data/store'; + +export interface ProjectSettingsDastAutomationQueryParams { + scenario_id: string; +} + +export default class AuthenticatedProjectSettingsDastAutomationRoute extends ScrollToTop( + Route +) { + @service declare store: Store; + + async model(params: ProjectSettingsDastAutomationQueryParams) { + const scenario = await this.store.findRecord( + 'scan-parameter-group', + params?.scenario_id + ); + + return { + project: this.modelFor('authenticated.project') as ProjectModel, + scenario, + }; + } +} diff --git a/app/styles/_component-variables.scss b/app/styles/_component-variables.scss index 2e8484bfdc..be1d3b1681 100644 --- a/app/styles/_component-variables.scss +++ b/app/styles/_component-variables.scss @@ -829,8 +829,11 @@ body { --project-settings-loading-container-border: var(--border-color-1); --project-settings-loading-container-border-radius: var(--border-radius); + // variables for project-settings/page-wrapper + --project-settings-page-wrapper-background-color: var(--background-light); + // variables for project-settings/header - --project-settings-header-background-color: var(--common-white); + --project-settings-header-background-color: var(--background-light); --project-settings-header-border-color: var(--border-color-1); --project-settings-header-border-radius: var(--border-radius); --project-settings-header-info-text-color: var(--text-secondary-color); @@ -841,6 +844,9 @@ body { // variables for project-settings/general-settings --project-settings-general-settings-root-border-color: var(--border-color-1); + --project-settings-general-settings-root-background-color: var( + --background-main + ); // variables for project-settings/general-settings/dynamicscan-automation-settings --project-settings-general-settings-dynamicscan-automation-settings-box-shadow: var( @@ -853,6 +859,14 @@ body { --border-color-1 ); + // variables for project-settings/general-settings/dynamicscan-automation-settings/scenario + --project-settings-general-settings-dynamicscan-automation-scenario-textfield-bg: var( + --neutral-grey-100 + ); + --project-settings-general-settings-dynamicscan-automation-scenario-form-footer-box-shadow: var( + --box-shadow-4 + ); + // variables for project-settings/general-settings/github-project --project-settings-general-settings-github-project-background-color: var( --neutral-grey-200 @@ -892,6 +906,25 @@ body { --border-radius ); + // variables for project-settings/view-scenario + --project-settings-view-scenario-background-white: var(--background-main); + --project-settings-view-scenario-background-dark: var(--neutral-grey-100); + --project-settings-view-scenario-modal-footer-box-shadow: var(--box-shadow-4); + --project-settings-view-scenario-border-light: var(--border-color-1); + --project-settings-view-scenario-header-border-color: var(--border-color-1); + --project-settings-view-scenario-header-border-radius: var(--border-radius); + --project-settings-view-scenario-header-scenario-details-box-shadow: var( + --box-shadow-8 + ); + + // variables for project-settings/view-scenario/parameter-item + --project-settings-view-scenario-parameter-item-border-color: var( + --neutral-grey-100 + ); + --project-settings-view-scenario-parameter-text-field-background: var( + --neutral-grey-100 + ); + // variables for tri-state-checkbox --tri-state-checkbox-overridden-bg: var(--success-light); --tri-state-checkbox-border-color: var(--success-main); diff --git a/app/styles/_icons.scss b/app/styles/_icons.scss index 62f617c224..aeb0737223 100644 --- a/app/styles/_icons.scss +++ b/app/styles/_icons.scss @@ -502,3 +502,15 @@ .ak-icon-desktop-windows { @extend .mi-desktop-windows; } + +.ak-icon-visibility { + @extend .mi-visibility; +} + +.ak-icon-visibility-off { + @extend .mi-visibility-off; +} + +.ak-icon-lock-open { + @extend .mi-lock-open; +} diff --git a/app/templates/authenticated/project/settings.hbs b/app/templates/authenticated/project/settings.hbs index afd5efde8c..b891bfa950 100644 --- a/app/templates/authenticated/project/settings.hbs +++ b/app/templates/authenticated/project/settings.hbs @@ -1,7 +1,5 @@ {{page-title 'Project Settings'}} -
- - + {{outlet}} -
\ No newline at end of file + \ No newline at end of file diff --git a/app/templates/authenticated/project/settings/analysis.hbs b/app/templates/authenticated/project/settings/analysis.hbs index 5edb990ffe..254bbcc483 100644 --- a/app/templates/authenticated/project/settings/analysis.hbs +++ b/app/templates/authenticated/project/settings/analysis.hbs @@ -1,3 +1,5 @@ {{page-title 'Analysis Setting'}} + + \ No newline at end of file diff --git a/app/templates/authenticated/project/settings/dast-automation-scenario.hbs b/app/templates/authenticated/project/settings/dast-automation-scenario.hbs new file mode 100644 index 0000000000..63aa0ae5b2 --- /dev/null +++ b/app/templates/authenticated/project/settings/dast-automation-scenario.hbs @@ -0,0 +1,11 @@ +{{page-title 'DAST Automation Scenario'}} + + + + \ No newline at end of file diff --git a/app/templates/authenticated/project/settings/index.hbs b/app/templates/authenticated/project/settings/index.hbs index 1ad23126f5..f4eaa87c47 100644 --- a/app/templates/authenticated/project/settings/index.hbs +++ b/app/templates/authenticated/project/settings/index.hbs @@ -1,3 +1,5 @@ {{page-title 'General Setting'}} + + \ No newline at end of file diff --git a/mirage/config.js b/mirage/config.js index b6ec624741..748a8e8e01 100644 --- a/mirage/config.js +++ b/mirage/config.js @@ -86,6 +86,14 @@ function routes() { this.namespace = config.namespace; + this.get('/v2/projects/:id', (schema, req) => { + return schema.projects.find(`${req.params.id}`)?.toJSON(); + }); + + this.get('/v2/files/:id', (schema, req) => { + return schema.files.find(`${req.params.id}`)?.toJSON(); + }); + this.get('/organizations/:id/projects', (schema) => { return schema.projects.all().models; }); @@ -308,8 +316,20 @@ function routes() { }); this.get('/profiles/:id/unknown_analysis_status', 'unknown-analysis-status'); - this.get('/users/:id', 'user'); - this.get('/users', 'user'); + + this.get('/users/:id', (schema, req) => { + return { + data: { + attributes: schema.users.find(`${req.params.id}`)?.toJSON(), + id: req.params.id, + relationships: {}, + type: 'users', + }, + }; + }); + + // this.get('/users', 'user'); + this.get('/projects/:id', 'project'); this.get('/projects', 'project'); this.get('/pricings', 'pricing'); @@ -324,9 +344,18 @@ function routes() { this.get('/submissions', 'submission'); this.get('/files/:id', 'file'); this.get('/vulnerabilities/:id', 'vulnerability'); + this.get('/vulnerabilities', (schema) => { - return schema.vulnerabilities.all().models; + return { + data: schema.vulnerabilities.all().models.map((model) => ({ + attributes: model, + id: model.id, + relationships: {}, + type: 'vulnerabilities', + })), + }; }); + this.get('/invitations/:id', 'invitation'); this.get('/devices', 'device'); this.get('/invoices', 'invoice'); @@ -604,7 +633,7 @@ function routes() { }); this.get('/organizations/:orgId/members/:memId', (schema, request) => { - return schema.organizationMembers.find(request.params.memId); + return schema.organizationMembers.find(request.params.memId)?.toJSON(); }); this.put('/organizations/:orgId/members/:memId', () => { @@ -631,12 +660,14 @@ function routes() { return {}; }); - this.get('/organizations/:id/me', function (schema, req) { - const currentUser = schema.currentUsers.findBy({ - organizationId: req.params.id, - }); - const me = schema.organizationMes.find(currentUser.id); - return this.serialize(me).organizationMe; + this.get('/organizations/:id/me', function (schema) { + // const currentUser = schema.currentUsers.findBy({ + // organizationId: req.params.id, + // }); + // const me = schema.organizationMes.find(currentUser.id); + // return this.serialize(me).organizationMe; + + return schema.organizationMes.all().models[0]?.toJSON(); }); this.put('/v2/am_configurations/:id', () => { diff --git a/mirage/factories/scan-parameter-group.js b/mirage/factories/scan-parameter-group.js new file mode 100644 index 0000000000..0da7941952 --- /dev/null +++ b/mirage/factories/scan-parameter-group.js @@ -0,0 +1,9 @@ +import Base from './base'; +import faker from 'faker'; + +export default Base.extend({ + name: faker.company.companyName(), + description: faker.lorem.sentence(), + is_active: () => faker.random.boolean(), + is_default: () => faker.random.boolean(), +}); diff --git a/mirage/factories/scan-parameter.js b/mirage/factories/scan-parameter.js new file mode 100644 index 0000000000..29ae6994b4 --- /dev/null +++ b/mirage/factories/scan-parameter.js @@ -0,0 +1,8 @@ +import faker from 'faker'; +import Base from './base'; + +export default Base.extend({ + name: faker.company.companyName(), + value: faker.lorem.slug(2), + is_secure: () => faker.random.boolean(), +}); diff --git a/mirage/models/scan-parameter-group.js b/mirage/models/scan-parameter-group.js new file mode 100644 index 0000000000..770b50936d --- /dev/null +++ b/mirage/models/scan-parameter-group.js @@ -0,0 +1,3 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({}); diff --git a/mirage/models/scan-parameter.js b/mirage/models/scan-parameter.js new file mode 100644 index 0000000000..770b50936d --- /dev/null +++ b/mirage/models/scan-parameter.js @@ -0,0 +1,3 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({}); diff --git a/mirage/scenarios/default.js b/mirage/scenarios/default.js index 2e1611e101..ad6d271bdf 100644 --- a/mirage/scenarios/default.js +++ b/mirage/scenarios/default.js @@ -22,7 +22,7 @@ export default function (server) { githubCount = 1, jiraCount = 1, vulnerabilityPreferenceCount = 10, - projectCount = getRandomInt(4, 5), + projectCount = getRandomInt(10, 15), project = null, file = null, projectIds = [], @@ -45,46 +45,220 @@ export default function (server) { server.createList('vulnerability-preference', vulnerabilityPreferenceCount); server.createList('available-device', availableDeviceCount); server.createList('organization', organizationCount); + server.create('organization-me'); server.create('organization-member'); - server.create('partnerclient-plan'); + for (var teamId = 1; teamId <= teamCount; teamId++) { server.create('team', { users: users, }); } + for (var projectId = 1; projectId <= projectCount; projectId++) { projectIds.push(projectId); + var fileCount = getRandomInt(1, 4); + project = server.create('project', { userId: currentUserId, + last_file_id: fileCount, }); + server.create('invitation', { projectId: projectId, userId: currentUserId, }); + var fileIds = []; + for (var fileId = 1; fileId <= fileCount; fileId++) { file = server.create('file', { projectId: projectId, }); + server.create('manualscan', { projectId: projectId, }); + fileIds.push(file.id); + for ( var vulnerabilityId = 1; vulnerabilityId <= vulnerabilityCount; vulnerabilityId++ ) { server.create('analysis', { - file: file, - vulnerabilityId: vulnerabilityId, + file: file.id, + vulnerability: vulnerabilityId, }); } } + project.fileIds = fileIds; } + + // DAST Automation Scenarios + server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_default: true, + }); + + server.create('scan-parameter', { + id: '1', + scanParameterGroup: 'default', + name: 'Username', + value: 'Appknox', + is_secure: false, + }); + + server.create('scan-parameter', { + id: '2', + scanParameterGroup: 'default', + name: 'Password', + value: 'unknown', + is_secure: true, + }); + + [ + { + hasRead: true, + messageCode: 'NF_SYSTM_FILE_UPLOAD_SUCCESS', + context: { + file_id: 450, + version: '1.0', + platform: 0, + package_name: 'com.appknox.mfva', + version_code: '6', + platform_display: 'Android', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLOAD_SUCCESS', + context: { + file_id: 450, + version: '1.0', + platform: 0, + package_name: 'com.appknox.mfva', + version_code: '6', + platform_display: 'Android', + store_url: 'https://play.google.com/mfva', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_VLDTN_ERR', + context: { + store_url: 'https://apps.apple.com/mfva', + error_message: 'Invalid URL. URL is not valid', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILPAYRQ1', + context: { + package_name: 'com.appknox.mfva', + store_url: 'https://play.google.com/mfva', + error_message: 'failed due to Insufficient Credits Error', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILPAY2', + context: { + package_name: 'com.appknox.mfva', + requester_username: 'test_user', + store_url: 'https://play.google.com/mfva', + error_message: 'failed due to Insufficient Credits Error', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILNSUNAPRV1', + context: { + namespace_value: 'com.appknox.mfva', + platform_display: 'Andriod', + platform: 0, + store_url: 'https://play.google.com/mfva', + error_message: 'failed due to unapproved namespace', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILNSCREATD1', + context: { + namespace_value: 'com.appknox.mfva', + platform_display: 'Andriod', + platform: 0, + store_url: 'https://play.google.com/mfva', + error_message: 'failed due to non-existent namespace', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILNPRJDENY2', + context: { + project_id: 1, + package_name: 'com.appknox.mfva', + platform_display: 'Andriod', + platform: 0, + requester_username: 'test_user', + requester_role: 'Admin', + store_url: 'https://play.google.com/mfva', + error_message: 'User does not have access to the project', + }, + }, + { + hasRead: true, + messageCode: 'NF_STR_URL_UPLDFAILNPRJDENY1', + context: { + package_name: 'com.appknox.mfva', + platform_display: 'Andriod', + platform: 0, + store_url: 'https://play.google.com/mfva', + error_message: "You don't have permission to upload to project", + }, + }, + ].forEach((notification) => { + server.create('nf-in-app-notification', notification); + }); + + const org_user_requestedby = server.create('organization-user', { + username: 'appknox_requester', + email: 'appknox_requester@test.com', + isActive: true, + }); + + const namespace = server.create('organization-namespace', { + value: 'com.mfva.test', + createdOn: new Date(), + approvedOn: null, + isApproved: false, + requestedBy: org_user_requestedby, + approvedBy: null, + platform: 1, + }); + + server.create('nf-in-app-notification', { + hasRead: true, + messageCode: 'NF_STR_URL_NSREQSTD1', + context: { + namespace_id: namespace.id, + namespace_created_on: new Date(), + namespace_value: 'com.mfva.test', + platform: 1, + platform_display: 'android', + requester_username: 'appknox_requester', + // initial_requester_username: 'appknox_requester_previous', + // current_requester_username: 'appknox_requester', + store_url: 'https://play.google.com/mfva', + }, + }); + var currentUser = server.db.users.get(currentUserId); + currentUser.projectIds = projectIds; } diff --git a/package-lock.json b/package-lock.json index 40452421bc..e12e91de2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31910,40 +31910,6 @@ "node": ">= 4" } }, - "node_modules/ember-factory-for-polyfill": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/ember-factory-for-polyfill/-/ember-factory-for-polyfill-1.3.1.tgz", - "integrity": "sha512-y3iG2iCzH96lZMTWQw6LWNLAfOmDC4pXKbZP6FxG8lt7GGaNFkZjwsf+Z5GAe7kxfD7UG4lVkF7x37K82rySGA==", - "dev": true, - "dependencies": { - "ember-cli-version-checker": "^2.1.0" - }, - "engines": { - "node": "^4.5 || 6.* || >= 7.*" - } - }, - "node_modules/ember-factory-for-polyfill/node_modules/ember-cli-version-checker": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz", - "integrity": "sha512-G+KtYIVlSOWGcNaTFHk76xR4GdzDLzAS4uxZUKdASuFX0KJE43C6DaqL+y3VTpUFLI2FIkAS6HZ4I1YBi+S3hg==", - "dev": true, - "dependencies": { - "resolve": "^1.3.3", - "semver": "^5.3.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ember-factory-for-polyfill/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/ember-fetch": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/ember-fetch/-/ember-fetch-8.1.2.tgz", @@ -32831,41 +32797,6 @@ "mkdirp": "bin/cmd.js" } }, - "node_modules/ember-getowner-polyfill": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ember-getowner-polyfill/-/ember-getowner-polyfill-2.2.0.tgz", - "integrity": "sha512-rwGMJgbGzxIAiWYjdpAh04Abvt0s3HuS/VjHzUFhVyVg2pzAuz45B9AzOxYXzkp88vFC7FPaiA4kE8NxNk4A4Q==", - "dev": true, - "dependencies": { - "ember-cli-version-checker": "^2.1.0", - "ember-factory-for-polyfill": "^1.3.1" - }, - "engines": { - "node": "^4.5 || 6.* || >= 7.*" - } - }, - "node_modules/ember-getowner-polyfill/node_modules/ember-cli-version-checker": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz", - "integrity": "sha512-G+KtYIVlSOWGcNaTFHk76xR4GdzDLzAS4uxZUKdASuFX0KJE43C6DaqL+y3VTpUFLI2FIkAS6HZ4I1YBi+S3hg==", - "dev": true, - "dependencies": { - "resolve": "^1.3.3", - "semver": "^5.3.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/ember-getowner-polyfill/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/ember-ignore-children-helper": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ember-ignore-children-helper/-/ember-ignore-children-helper-1.0.1.tgz", diff --git a/tests/integration/components/dynamic-scan-test.js b/tests/integration/components/dynamic-scan-test.js index 1e5acbd869..9797a6a3fb 100644 --- a/tests/integration/components/dynamic-scan-test.js +++ b/tests/integration/components/dynamic-scan-test.js @@ -463,7 +463,7 @@ module( assert .dom('[data-test-dynamicScanModal-automatedDynamicScanAppiumNote]') - .hasText('t:appiumScriptsNote:()'); + .hasText('t:dynScanAutoSchedNote:()'); assert .dom('[data-test-dynamicScanModal-automatedDynamicScanScheduleBtn]') diff --git a/tests/integration/components/project-settings/dast-scenario-toggle-test.js b/tests/integration/components/project-settings/dast-scenario-toggle-test.js new file mode 100644 index 0000000000..6e66eb3ff4 --- /dev/null +++ b/tests/integration/components/project-settings/dast-scenario-toggle-test.js @@ -0,0 +1,137 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, find, render, waitFor, waitUntil } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import Service from '@ember/service'; +import { Response } from 'miragejs'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +module( + 'Integration | Component | project-settings/dast-scenario-toggle', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + this.owner.register('service:notifications', NotificationsStub); + const store = this.owner.lookup('service:store'); + + // Project Model + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + // Scenario Model + const scenario = this.server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_active: false, + }); + + const normalizedScenario = store.normalize('scan-parameter-group', { + ...scenario.toJSON(), + }); + + // Selectors + const statusToggleSelector = + '[data-test-projectSettings-dastScenario-toggle] [data-test-toggle-input]'; + + const statusToggleLoaderSelector = + '[data-test-projectSettings-dastScenario-statusToggleLoading]'; + + this.setProperties({ + project: store.push(normalizedProject), + scenario: store.push(normalizedScenario), + statusToggleSelector, + statusToggleLoaderSelector, + }); + }); + + test('it renders and toggles dast scenario successfully', async function (assert) { + this.server.put( + '/v2/projects/:projectId/scan_parameter_groups/:id', + function (schema, request) { + const { is_active, ...rest } = JSON.parse(request.requestBody); + const id = request.params.id; + + return schema.scanParameterGroups + .find(id) + .update({ is_active: is_active === 'true', ...rest }); + }, + { timing: 150 } + ); + + await render( + hbs`` + ); + + assert.dom(this.statusToggleSelector).exists().isNotChecked(); + + click(this.statusToggleSelector); + + await waitFor(this.statusToggleLoaderSelector, { + timeout: 150, + }); + + assert.dom(this.statusToggleLoaderSelector).exists(); + + await waitUntil(() => !find(this.statusToggleLoaderSelector), { + timeout: 150, + }); + + assert.dom(this.statusToggleSelector).exists().isChecked(); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual( + notify.successMsg, + 't:dastAutomation.scenarioStatusUpdated:()' + ); + }); + + test('it reverts toggle state if request fails', async function (assert) { + const errorMessage = 'disconnect error'; + + this.server.put( + '/v2/projects/:projectId/scan_parameter_groups/:id', + () => new Response(400, {}, { detail: errorMessage }) + ); + + await render( + hbs`` + ); + + assert.dom(this.statusToggleSelector).exists().isNotChecked(); + + await click(this.statusToggleSelector); + + assert.dom(this.statusToggleSelector).exists().isNotChecked(); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual(notify.errorMsg, errorMessage); + }); + } +); diff --git a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js new file mode 100644 index 0000000000..70c5ba51b2 --- /dev/null +++ b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/index-test.js @@ -0,0 +1,143 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import Service from '@ember/service'; +import { objectifyEncodedReqBody } from 'irene/tests/test-utils'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +const selectors = { + dynScanAutoRoot: + '[data-test-projectSettings-genSettings-dynScanAutoSettings-root]', + dynScanAutoToggle: + '[data-test-genSettings-dynScanAutoSettings-dynamicscanModeToggle] [data-test-toggle-input]', + dynamicscanModeToggleLabel: + '[data-test-genSettings-dynScanAutoSettings-dynamicscanModeToggleLabel]', +}; + +module( + 'Integration | Component | project-settings/general-settings/dynamicscan-automation-settings', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + // Server mocks + this.server.get('/v2/projects/:id', (schema, req) => { + return schema.projects.find(req.params.id).toJSON(); + }); + + this.server.get('/v2/files/:id', (schema, req) => { + return schema.files.find(`${req.params.id}`)?.toJSON(); + }); + + this.server.get('/profiles/:id/dynamicscan_mode', (schema, req) => + schema.dynamicscanModes.find(`${req.queryParams.id}`)?.toJSON() + ); + + this.owner.register('service:notifications', NotificationsStub); + + const store = this.owner.lookup('service:store'); + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + this.setProperties({ + project: store.push(normalizedProject), + }); + }); + + test('it renders', async function (assert) { + this.server.create('dynamicscan-mode', { + id: 1, + dynamicscan_mode: 'Manual', + }); + + await render(hbs` + + `); + + assert.dom(selectors.dynScanAutoRoot).exists(); + + assert + .dom('[data-test-genSettings-dynScanAutoSettings-headerTitle]') + .exists() + .hasText('t:dynamicScanAutomation:()'); + + assert + .dom('[data-test-genSettings-dynScanAutoSettings-headerInfoChip]') + .exists() + .hasText('t:experimentalFeature:()'); + + assert + .dom('[data-test-genSettings-dynScanAutoSettings-headerInfoDescNote]') + .exists() + .hasText('t:dynScanAutoSchedNote:()'); + }); + + test('it toggles scheduled automation', async function (assert) { + this.server.put('/profiles/:id/dynamicscan_mode', (schema, req) => { + const reqBody = objectifyEncodedReqBody(req.requestBody); + + this.set('dynamicscan_mode', reqBody.dynamicscan_mode); + + return { + dynamicscan_mode: reqBody.dynamicscan_mode, + id: req.params.id, + }; + }); + + await render(hbs` + + `); + + assert.dom(selectors.dynScanAutoRoot).exists(); + + assert + .dom(selectors.dynamicscanModeToggleLabel) + .exists() + .containsText('t:appiumScheduledAutomation:()'); + + assert.dom(selectors.dynScanAutoToggle).exists().isNotChecked(); + + await click(selectors.dynScanAutoToggle); + + assert.dom(selectors.dynScanAutoToggle).isChecked(); + + assert.ok(this.dynamicscan_mode, 'Automated'); + + await click(selectors.dynScanAutoToggle); + + assert.dom(selectors.dynScanAutoToggle).isNotChecked(); + + assert.ok(this.dynamicscan_mode, 'Manual'); + }); + } +); diff --git a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-test.js b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-test.js new file mode 100644 index 0000000000..eed2cfdc96 --- /dev/null +++ b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scenario-test.js @@ -0,0 +1,572 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { + click, + fillIn, + find, + findAll, + render, + triggerEvent, + waitFor, + waitUntil, +} from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl, t } from 'ember-intl/test-support'; +import Service from '@ember/service'; +import { Response } from 'ember-cli-mirage'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +class RouterStub extends Service { + currentRouteName = 'authenticated.project.settings'; + scenarioId = ''; + + transitionTo(routeName, scenarioId) { + this.currentRouteName = routeName; + this.scenarioId = scenarioId; + } +} + +const selectors = { + scenariosRoot: '[data-test-genSettings-dynScanAutoSettings-scenarios-root]', + scenariosListEmpty: + '[data-test-genSettings-dynScanAutoSettings-scenariosListEmpty]', + scenarioListLoader: + '[data-test-genSettings-dynScanAutoSettings-scenarioListLoader]', + scenariosHeaderText: + '[data-test-genSettings-dynScanAutoSettings-scenariosHeaderText]', + scenariosListDesc: + '[data-test-genSettings-dynScanAutoSettings-scenariosHeaderListDesc]', + addScenarioBtn: '[data-test-genSettings-dynScanAutoSettings-scenarioAddBtn]', + addScenarioBtnIcon: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddBtnIcon]', + + // Modal Selectors + scenarioNameTextfieldTitle: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-scenarioNameTextfieldTitle]', + scenarioNameTextfield: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-scenarioNameTextfield]', + scenarioStatusTitle: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-scenarioStatusTitle]', + scenarioAddConfirmBtn: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-confirmBtn]', + scenarioAddCancelBtn: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-cancelBtn]', + scenarioAddStatusToggle: + '[data-test-genSettings-dynScanAutoSettings-scenarioAddModal-scenarioStatusToggle] [data-test-toggle-input]', + + // Scenarios Table specific + statusHeaderTooltip: + '[data-test-genSettings-dynScanAutoSettings-statusColumnHeader-tooltip]', + statusHeaderTooltipContent: + '[data-test-genSettings-dynScanAutoSettings-statusColumnHeader-tooltipContent]', + statusHeaderTooltipIcon: + '[data-test-genSettings-dynScanAutoSettings-statusColumnHeader-tooltipIcon]', + scenarioListEmpty: + '[data-test-genSettings-dynScanAutoSettings-scenariosListEmpty]', + scenarioStatusToggle: + '[data-test-projectSettings-dastScenario-toggle] [data-test-toggle-input]', + scenarioTableHeader: + '[data-test-genSettings-dynScanAutoSettings-scenarioTableHeader] th', + scenarioTableRow: + '[data-test-genSettings-dynScanAutoSettings-scenarioTableRow]', +}; + +module( + 'Integration | Component | project-settings/general-settings/dynamicscan-automation-settings/scenario', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + // Server mocks + this.server.get('/v2/projects/:id', (schema, req) => { + return schema.projects.find(req.params.id).toJSON(); + }); + + this.server.get('/v2/files/:id', (schema, req) => { + return schema.files.find(`${req.params.id}`)?.toJSON(); + }); + + this.server.get('/v2/scan_parameter_groups/:id', (schema, req) => + schema.scanParameterGroups.find(req.params.id).toJSON() + ); + + this.server.get( + '/v2/projects/:projectId/scan_parameter_groups/:id', + (schema, req) => schema.scanParameterGroups.find(req.params.id).toJSON() + ); + + this.server.get( + '/v2/projects/:projectId/scan_parameter_groups', + function (schema, request) { + const data = schema.scanParameterGroups.all().models; + + let limit = data.length; + let offset = 0; + + if (request.queryParams.limit) { + limit = request.queryParams.limit; + } + + if (request.queryParams.offset) { + offset = request.queryParams.offset; + } + + const retdata = data.slice(offset, offset + limit); + + return { + count: data.length, + next: null, + previous: null, + results: retdata, + }; + } + ); + + this.server.post( + '/v2/projects/:projectId/scan_parameter_groups', + function (schema, request) { + const { name, description } = JSON.parse(request.requestBody); + + const nameIsAvailable = schema.scanParameterGroups + .all() + .models.find((g) => { + return g.name.toLowerCase() === name.toLowerCase(); + }); + + if (nameIsAvailable) { + return new Response( + 400, + {}, + { + detail: 'Scenario Name already exists', + } + ); + } + + return schema.scanParameterGroups + .create({ + name, + description, + project: request.params.projectId, + }) + .toJSON(); + } + ); + + this.server.put( + '/v2/projects/:projectId/scan_parameter_groups/:id', + function (schema, request) { + const { is_active, ...rest } = JSON.parse(request.requestBody); + const id = request.params.id; + + return schema.scanParameterGroups + .find(id) + .update({ is_active, ...rest }); + } + ); + + this.server.delete( + '/v2/projects/:projectId/scan_parameter_groups/:id', + function (schema, request) { + const scanParameterGroup = schema.scanParameterGroups.find( + request.params.id + ); + + if (!scanParameterGroup) { + return new Response( + 404, + {}, + { + detail: 'Not found.', + } + ); + } + + scanParameterGroup.destroy(); + return new Response(204, {}, ''); + } + ); + + // Service registers + this.owner.register('service:notifications', NotificationsStub); + this.owner.register('service:router', RouterStub); + + // Records + const store = this.owner.lookup('service:store'); + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + this.setProperties({ + project: store.push(normalizedProject), + }); + }); + + test('it renders', async function (assert) { + this.server.get( + '/v2/projects/:projectId/scan_parameter_groups', + function (schema, request) { + const data = schema.scanParameterGroups.all().models; + + let limit = data.length; + let offset = 0; + + if (request.queryParams.limit) { + limit = request.queryParams.limit; + } + + if (request.queryParams.offset) { + offset = request.queryParams.offset; + } + + const retdata = data.slice(offset, offset + limit); + + return { + count: data.length, + next: null, + previous: null, + results: retdata, + }; + }, + { timing: 150 } + ); + + render(hbs` + + `); + + await waitFor(selectors.scenarioListLoader, { + timeout: 150, + }); + + assert.dom(selectors.scenarioListLoader).exists(); + + await waitUntil(() => !find(selectors.scenarioListLoader), { + timeout: 150, + }); + + assert.dom(selectors.scenariosRoot).exists(); + + assert + .dom(selectors.scenariosHeaderText) + .exists() + .containsText(t('dastAutomation.automationScenarios')); + + assert + .dom(selectors.scenariosListDesc) + .exists() + .containsText(t('dastAutomation.scenarioListDesc')); + + assert + .dom(selectors.addScenarioBtn) + .exists() + .containsText(t('dastAutomation.addScenario')); + + assert.dom(selectors.addScenarioBtnIcon).exists(); + }); + + test('it adds and renders project scenarios', async function (assert) { + const scenario1Name = 'New Scenario Name 1'; + const scenario2Name = 'New Scenario Name 2'; + + await render(hbs` + + `); + + assert + .dom(selectors.scenarioListEmpty) + .exists() + .containsText(t('dastAutomation.noScenariosFound')); + + await click(selectors.addScenarioBtn); + + assert + .dom(selectors.scenarioNameTextfieldTitle) + .exists() + .containsText(t('scenario')); + + assert.dom(selectors.scenarioNameTextfield).exists(); + + assert + .dom(selectors.scenarioStatusTitle) + .exists() + .containsText(t('status')); + + assert.dom(selectors.scenarioAddStatusToggle).exists().isNotChecked(); + + assert + .dom(selectors.scenarioAddConfirmBtn) + .exists() + .containsText(t('add')) + .isDisabled(); + + assert + .dom(selectors.scenarioAddCancelBtn) + .exists() + .containsText(t('cancel')); + + assert + .dom(selectors.scenarioListEmpty) + .containsText(t('dastAutomation.noScenariosFound')); + + // Add first scenario + await fillIn(selectors.scenarioNameTextfield, scenario1Name); + + assert.dom(selectors.scenarioAddConfirmBtn).isNotDisabled(); + + await click(selectors.scenarioAddConfirmBtn); + + const notify = this.owner.lookup('service:notifications'); + assert.strictEqual(notify.successMsg, t('dastAutomation.scenarioAdded')); + + // Add second scenario + await click(selectors.addScenarioBtn); + + await fillIn(selectors.scenarioNameTextfield, scenario2Name); + + await click(selectors.scenarioAddStatusToggle); + assert.dom(selectors.scenarioAddStatusToggle).isChecked(); + + await click(selectors.scenarioAddConfirmBtn); + + assert.dom(selectors.scenarioNameTextfieldTitle).doesNotExist(); + + // Sanity Check for scenario header + const scenariosHeaderCells = findAll(selectors.scenarioTableHeader); + + assert.strictEqual(scenariosHeaderCells.length, 2); + + assert.dom(scenariosHeaderCells[0]).containsText(t('scenario')); + assert.dom(scenariosHeaderCells[1]).containsText(t('status')); + + assert + .dom( + scenariosHeaderCells[1].querySelector( + selectors.statusHeaderTooltipIcon + ) + ) + .exists(); + + await triggerEvent( + scenariosHeaderCells[1].querySelector(selectors.statusHeaderTooltip), + 'mouseenter' + ); + + assert + .dom(selectors.statusHeaderTooltipContent) + .exists() + .containsText(t('dastAutomation.statusColumnToggleInfo')); + + // Sanity Check for scenario items + const rows = findAll(selectors.scenarioTableRow); + + assert.strictEqual( + rows.length, + this.server.db.scanParameterGroups.length + ); + + // assert first row + const firstRowCells = rows[0]; + + assert.dom(firstRowCells).containsText(scenario1Name); + assert.dom(selectors.scenarioStatusToggle, firstRowCells).isNotChecked(); + + // assert second row + const secondRowCells = rows[1]; + + assert.dom(secondRowCells).containsText(scenario2Name); + assert.dom(selectors.scenarioStatusToggle, secondRowCells).isChecked(); + }); + + test('it throws an error when adding a scenario with the same name', async function (assert) { + const scenario1Name = 'New Scenario Name 1'; + const dupNameError = 'Scenario Name already exists'; + + this.server.post( + '/v2/projects/:projectId/scan_parameter_groups', + function (schema, request) { + const { name, description } = JSON.parse(request.requestBody); + + const nameIsAvailable = schema.scanParameterGroups + .all() + .models.find((g) => { + return g.name.toLowerCase() === name.toLowerCase(); + }); + + if (nameIsAvailable) { + return new Response(400, {}, { name: [dupNameError] }); + } + + return schema.scanParameterGroups + .create({ + name, + description, + project: request.params.projectId, + }) + .toJSON(); + } + ); + + await render(hbs` + + `); + + await click(selectors.addScenarioBtn); + + // Add first scenario + await fillIn(selectors.scenarioNameTextfield, scenario1Name); + + assert.dom(selectors.scenarioAddConfirmBtn).isNotDisabled(); + + await click(selectors.scenarioAddConfirmBtn); + + const notify = this.owner.lookup('service:notifications'); + assert.strictEqual(notify.successMsg, t('dastAutomation.scenarioAdded')); + + // Add second scenario with same name + await click(selectors.addScenarioBtn); + + await fillIn(selectors.scenarioNameTextfield, scenario1Name); + + await click(selectors.scenarioAddConfirmBtn); + assert.strictEqual(notify.errorMsg, dupNameError); + + await click(selectors.scenarioAddCancelBtn); + + // Sanity Check for scenario items + const rows = findAll(selectors.scenarioTableRow); + + assert.strictEqual(rows.length, 1); + assert.strictEqual(this.server.db.scanParameterGroups.length, 1); + + // assert first row + const firstRowCells = rows[0]; + + assert.dom(firstRowCells).containsText(scenario1Name); + assert.dom(selectors.scenarioStatusToggle, firstRowCells).isNotChecked(); + }); + + test('it toggles project scenario statuses', async function (assert) { + const scenario1Name = 'New Scenario Name 1'; + + await render(hbs` + + `); + + await click(selectors.addScenarioBtn); + + // Add first scenario with status of false + await fillIn(selectors.scenarioNameTextfield, scenario1Name); + + assert.dom(selectors.scenarioAddConfirmBtn).isNotDisabled(); + + await click(selectors.scenarioAddConfirmBtn); + + const notify = this.owner.lookup('service:notifications'); + assert.strictEqual(notify.successMsg, t('dastAutomation.scenarioAdded')); + + // Sanity Check for scenario items + const rows = findAll(selectors.scenarioTableRow); + + // assert first row + const firstRowCells = rows[0]; + + assert.dom(firstRowCells).containsText(scenario1Name); + assert.dom(selectors.scenarioStatusToggle, firstRowCells).isNotChecked(); + + // Toggle first row status + assert.notOk(this.server.db.scanParameterGroups[0].is_active); + + await click(firstRowCells.querySelector(selectors.scenarioStatusToggle)); + + assert.dom(selectors.scenarioStatusToggle, firstRowCells).isChecked(); + + assert.strictEqual( + notify.successMsg, + t('dastAutomation.scenarioStatusUpdated') + ); + + assert.ok(this.server.db.scanParameterGroups[0].is_active); + }); + + test('it navigates to project scenario page on scenario click', async function (assert) { + const scenario1Name = 'New Scenario Name 1'; + + await render(hbs` + + `); + const router = this.owner.lookup('service:router'); + + // In Project settings route + assert.strictEqual( + router.currentRouteName, + 'authenticated.project.settings' + ); + + await click(selectors.addScenarioBtn); + + // Add first scenario with status of false + await fillIn(selectors.scenarioNameTextfield, scenario1Name); + await click(selectors.scenarioAddConfirmBtn); + + // Sanity Check for scenario items + const rows = findAll(selectors.scenarioTableRow); + + // assert first row + const firstRowCells = rows[0]; + + assert.dom(firstRowCells).containsText(scenario1Name); + assert.dom(selectors.scenarioStatusToggle, firstRowCells).isNotChecked(); + + // Click first row item + await click(firstRowCells); + + // In DAST Automation route + assert.strictEqual( + router.currentRouteName, + 'authenticated.project.settings.dast-automation-scenario' + ); + + assert.strictEqual( + router.scenarioId, + this.server.db.scanParameterGroups[0].id + ); + }); + } +); diff --git a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings-test.js b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived-test.js similarity index 93% rename from tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings-test.js rename to tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived-test.js index 9183223eee..b4ac855f26 100644 --- a/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings-test.js +++ b/tests/integration/components/project-settings/general-settings/dynamicscan-automation-settings/scripts-archived-test.js @@ -34,7 +34,7 @@ const getRequestObj = (body) => ); module( - 'Integration | Component | project-settings/general-settings/dynamicscan-automation-settings', + 'Integration | Component | project-settings/general-settings/dynamicscan-automation-settings/scripts-archived', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); @@ -89,9 +89,12 @@ module( dynamicscan_mode: 'Manual', }); - await render( - hbs`` - ); + await render(hbs` + + `); assert .dom('[data-test-projectSettings-genSettings-dynScanAutoSettings-root]') @@ -120,7 +123,7 @@ module( assert .dom('[data-test-genSettings-dynScanAutoSettings-headerInfoDescNote]') .exists() - .hasText('t:appiumScriptsNote:()'); + .hasText('t:appiumScriptsSchedNote:()'); }); test.each( @@ -178,9 +181,12 @@ module( this.set('fileName', 'test.zip'); - await render( - hbs`` - ); + await render(hbs` + + `); assert .dom('[data-test-genSettings-dynScanAutoSettings-uploadZip]') @@ -317,9 +323,12 @@ module( this.set('fileName', 'test.zip'); - await render( - hbs`` - ); + await render(hbs` + + `); let file = new File(['Test zip file'], this.fileName, { type: 'application/zip', diff --git a/tests/integration/components/project-settings/header-test.js b/tests/integration/components/project-settings/header-test.js index 2ff04c0da7..9059bd29fa 100644 --- a/tests/integration/components/project-settings/header-test.js +++ b/tests/integration/components/project-settings/header-test.js @@ -10,81 +10,97 @@ module('Integration | Component | project-settings/header', function (hooks) { setupMirage(hooks); setupIntl(hooks); - test('it renders', async function (assert) { - // Server mocks - this.server.get('/v2/projects/:id', (schema, req) => { - return schema.projects.find(req.params.id).toJSON(); - }); - - this.server.get('/v2/files/:id', (schema, req) => { - return schema.files.find(`${req.params.id}`)?.toJSON(); - }); - - const store = this.owner.lookup('service:store'); - const file = this.server.create('file', 1); - const project = this.server.create('project', { - id: 1, - last_file_id: file.id, - }); - - const normalizedProject = store.normalize('project', { - ...project.toJSON(), - }); - - this.project = store.push(normalizedProject); - - await render(hbs``); - - const breadcrumbItems = [ - 't:allProjects:()', - this.project.get('packageName'), - 't:settings:()', - ]; - - // Checks rendering of breadcrumbs - breadcrumbItems.forEach((item) => { - assert - .dom(`[data-test-projectSettingsHeader-breadcrumbItem="${item}"]`) - .exists(); - }); - - assert - .dom('[data-test-projectSettingsHeader-descText]') - .exists() - .containsText('t:settings:()') - .containsText('t:projectSettings.headerText:()'); - - assert.dom('[data-test-projectSettingsHeader-projectDetails]').exists(); - - assert - .dom('[data-test-projectSettingsHeader-projectDetails-lastFileIconUrl]') - .exists() - .hasAttribute('src', this.project.get('lastFile').get('iconUrl')); - - assert - .dom('[data-test-projectSettingsHeader-projectDetails-packageName]') - .exists() - .containsText(this.project.get('packageName')); - - assert - .dom('[data-test-projectSettingsHeader-projectDetails-projectID]') - .exists() - .containsText('t:projectID:()') - .containsText(this.project.id); - - assert - .dom('[data-test-projectSettingsHeader-platformIcon]') - .exists() - .hasClass(RegExp(`platform-${this.project.get('platformIconClass')}`)); - - // Checks rendering of tabs - const tabItems = ['t:generalSettings:()', 't:analysisSettings:()']; - - tabItems.forEach((tab) => { - assert - .dom(`[data-test-projectSettingsHeader-tab="${tab}"]`) - .exists() - .hasText(tab); - }); - }); + test.each( + 'it renders', + [true, false], + async function (assert, isDASTScenarioPage) { + this.set('isDASTScenarioPage', isDASTScenarioPage); + + // Server mocks + this.server.get('/v2/projects/:id', (schema, req) => { + return schema.projects.find(req.params.id).toJSON(); + }); + + this.server.get('/v2/files/:id', (schema, req) => { + return schema.files.find(`${req.params.id}`)?.toJSON(); + }); + + const store = this.owner.lookup('service:store'); + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + this.project = store.push(normalizedProject); + + await render( + hbs`` + ); + + const breadcrumbItems = [ + 't:allProjects:()', + this.project.get('packageName'), + isDASTScenarioPage + ? 't:dastAutomation.dastAutomationScenario:()' + : 't:settings:()', + ]; + + // Checks rendering of breadcrumbs + breadcrumbItems.forEach((item) => { + assert + .dom(`[data-test-projectSettingsHeader-breadcrumbItem="${item}"]`) + .exists(); + }); + + if (!isDASTScenarioPage) { + assert + .dom('[data-test-projectSettingsHeader-descText]') + .exists() + .containsText('t:settings:()') + .containsText('t:projectSettings.headerText:()'); + + assert.dom('[data-test-projectSettingsHeader-projectDetails]').exists(); + + assert + .dom( + '[data-test-projectSettingsHeader-projectDetails-lastFileIconUrl]' + ) + .exists() + .hasAttribute('src', this.project.get('lastFile').get('iconUrl')); + + assert + .dom('[data-test-projectSettingsHeader-projectDetails-packageName]') + .exists() + .containsText(this.project.get('packageName')); + + assert + .dom('[data-test-projectSettingsHeader-projectDetails-projectID]') + .exists() + .containsText('t:projectID:()') + .containsText(this.project.id); + + assert + .dom('[data-test-projectSettingsHeader-platformIcon]') + .exists() + .hasClass( + RegExp(`platform-${this.project.get('platformIconClass')}`) + ); + + // Checks rendering of tabs + const tabItems = ['t:generalSettings:()', 't:analysisSettings:()']; + + tabItems.forEach((tab) => { + assert + .dom(`[data-test-projectSettingsHeader-tab="${tab}"]`) + .exists() + .hasText(tab); + }); + } + } + ); }); diff --git a/tests/integration/components/project-settings/view-scenario/add-parameter-form-test.js b/tests/integration/components/project-settings/view-scenario/add-parameter-form-test.js new file mode 100644 index 0000000000..bd55d1b9e0 --- /dev/null +++ b/tests/integration/components/project-settings/view-scenario/add-parameter-form-test.js @@ -0,0 +1,258 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, fillIn, render, triggerEvent } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl, t } from 'ember-intl/test-support'; +import Service from '@ember/service'; +import { Response } from 'ember-cli-mirage'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +// Selectors +const selectors = { + addParamBtnSelector: '[data-test-projectSettings-viewScenario-addParamerBtn]', + inputTypeTextField: + '[data-test-projectSettings-viewScenario-inputTypeTextField]', + inputTypeClearBtn: + '[data-test-projectSettings-viewScenario-inputTypeTextField-clearBtn]', + inputValueClearBtn: + '[data-test-projectSettings-viewScenario-inputValueTextField-clearBtn]', + inputValueTextField: + '[data-test-projectSettings-viewScenario-inputValueTextField]', + inputTypeErrorIcon: + '[data-test-projectSettings-viewScenario-inputValueTextField]', + inputTypeErrorTooltip: + '[data-test-projectSettings-viewScenario-inputTypeTextField-errorTooltip]', + dupInputTypeErrorText: + '[data-test-projectSettings-viewScenario-inputTypeTextField-errorText]', +}; + +module( + 'Integration | Component | project-settings/view-scenario/add-parameter-form', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + this.owner.register('service:notifications', NotificationsStub); + + const store = this.owner.lookup('service:store'); + + // Scenario Model - Sets default to true + const scenario = this.server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_active: false, + is_default: true, + }); + + const normalizedScenario = store.normalize('scan-parameter-group', { + ...scenario.toJSON(), + }); + + this.setProperties({ + scenario: store.push(normalizedScenario), + reloadParameterList: () => {}, + inputType: 'input type', + inputValue: 'input value', + }); + }); + + test('it renders', async function (assert) { + await render(hbs` + + `); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .containsText('t:add:()') + .hasAttribute('disabled'); + + assert.dom(selectors.inputTypeTextField).exists(); + assert.dom(selectors.inputValueTextField).exists(); + }); + + test('it enables add button if input type and value textfields are filled', async function (assert) { + await render(hbs` + + `); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .hasAttribute('disabled'); + + await fillIn(selectors.inputTypeTextField, this.inputType); + await fillIn(selectors.inputValueTextField, this.inputValue); + + assert.dom(selectors.inputTypeTextField).hasValue(this.inputType); + assert.dom(selectors.inputValueTextField).hasValue(this.inputValue); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .doesNotHaveAttribute('disabled'); + }); + + test('it adds parameter to scenario', async function (assert) { + assert.expect(10); + + this.setProperties({ + // triggers parameter list reload if parameter is added successfully + reloadParameterList: () => { + assert.ok(true); + }, + }); + + const inputType = 'input type'; + const inputValue = 'input value'; + + this.server.post( + '/v2/scan_parameter_groups/:groupId/scan_parameters', + function (schema, request) { + const { name, value } = JSON.parse(request.requestBody); + + assert.strictEqual(name, inputType); + assert.strictEqual(value, inputValue); + + return schema.scanParameters + .create({ + name, + value, + is_secure: false, + scanParameterGroup: request.params.groupId, + }) + .toJSON(); + } + ); + + await render(hbs` + + `); + + await fillIn(selectors.inputTypeTextField, inputType); + await fillIn(selectors.inputValueTextField, inputValue); + + await click(selectors.addParamBtnSelector); + + assert.dom(selectors.inputTypeTextField).hasValue(''); + assert.dom(selectors.inputValueTextField).hasValue(''); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual(notify.successMsg, t('dastAutomation.parameterAdded')); + + assert.strictEqual(this.server.db.scanParameters.length, 1); + + const createdScanParameter = this.server.db.scanParameters[0]; + + assert.strictEqual(createdScanParameter.name, inputType); + assert.strictEqual(createdScanParameter.value, inputValue); + assert.notOk(createdScanParameter.is_secure); + }); + + test('it shows parameter name errors in inputType textfield helper if thrown', async function (assert) { + const duplicateNameError = 'Name already exists'; + + this.server.post( + '/v2/scan_parameter_groups/:groupId/scan_parameters', + function () { + return new Response( + 400, + {}, + { + name: duplicateNameError, + } + ); + } + ); + + await render(hbs` + + `); + + await fillIn(selectors.inputTypeTextField, this.inputType); + await fillIn(selectors.inputValueTextField, this.inputValue); + + await click(selectors.addParamBtnSelector); + + assert + .dom('[data-test-helper-text]') + .exists() + .containsText(duplicateNameError); + + assert.dom(selectors.inputTypeErrorIcon).exists(); + + await triggerEvent(selectors.inputTypeErrorTooltip, 'mouseenter'); + + assert + .dom(selectors.dupInputTypeErrorText) + .containsText( + t('dastAutomation.paramTypeDupText', { type: this.inputType }) + ); + }); + + test('it clears input type and value textfields onClear button click', async function (assert) { + await render(hbs` + + `); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .hasAttribute('disabled'); + + await fillIn(selectors.inputTypeTextField, this.inputType); + await fillIn(selectors.inputValueTextField, this.inputValue); + + assert.dom(selectors.inputTypeTextField).hasValue(this.inputType); + assert.dom(selectors.inputValueTextField).hasValue(this.inputValue); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .doesNotHaveAttribute('disabled'); + + await click(selectors.inputTypeClearBtn); + await click(selectors.inputValueClearBtn); + + assert.dom(selectors.inputTypeTextField).hasValue(''); + assert.dom(selectors.inputValueTextField).hasValue(''); + + assert + .dom(selectors.addParamBtnSelector) + .exists() + .hasAttribute('disabled'); + }); + } +); diff --git a/tests/integration/components/project-settings/view-scenario/details-column-header-test.js b/tests/integration/components/project-settings/view-scenario/details-column-header-test.js new file mode 100644 index 0000000000..1212493564 --- /dev/null +++ b/tests/integration/components/project-settings/view-scenario/details-column-header-test.js @@ -0,0 +1,55 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, triggerEvent } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupIntl } from 'ember-intl/test-support'; + +const selectors = { + inputTypeColumnHeaderTooltip: + '[data-test-projectSettings-scenarioDetails-inputTypeColumnHeader-tooltip]', + inputTypeColumnHeaderTooltipContent: + '[data-test-projectSettings-scenarioDetails-inputTypeColumnHeader-tooltipContent]', + inputTypeColumnHeaderTooltipIcon: + '[data-test-projectSettings-scenarioDetails-inputTypeColumnHeader-tooltipIcon]', + inputTypeColumnHeader: + '[data-test-projectSettings-scenarioDetails-inputTypeColumnHeader]', + inputValueColumnHeader: + '[data-test-projectSettings-scenarioDetails-inputValueColumnHeader]', +}; + +module( + 'Integration | Component | project-settings/view-scenario/details-column-header', + function (hooks) { + setupRenderingTest(hooks); + setupIntl(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + `); + + assert + .dom(selectors.inputTypeColumnHeader) + .exists() + .containsText('t:dastAutomation.inputType:()'); + + // Tootlip selector input type column + await triggerEvent(selectors.inputTypeColumnHeaderTooltip, 'mouseenter'); + + assert + .dom(selectors.inputTypeColumnHeaderTooltipContent) + .exists() + .containsText('t:dastAutomation.inputTypeColumnHeaderInfo:()'); + + assert.dom(selectors.inputTypeColumnHeaderTooltipIcon).exists(); + + assert + .dom(selectors.inputValueColumnHeader) + .exists() + .containsText('t:dastAutomation.inputValue:()'); + }); + } +); diff --git a/tests/integration/components/project-settings/view-scenario/header-test.js b/tests/integration/components/project-settings/view-scenario/header-test.js new file mode 100644 index 0000000000..4b504388e6 --- /dev/null +++ b/tests/integration/components/project-settings/view-scenario/header-test.js @@ -0,0 +1,258 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { click, find, render, triggerEvent } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl, t } from 'ember-intl/test-support'; +import Service from '@ember/service'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +class RouterStub extends Service { + currentRouteName = 'authenticated.project.settings.dast-automation-scenario'; + projectId = ''; + + transitionTo(routeName, projectId) { + this.currentRouteName = routeName; + this.projectId = projectId; + } +} + +const selectors = { + headerRoot: '[data-test-projectSettings-viewScenario-header-root]', + headerScenarioName: + '[data-test-projectSettings-viewScenarioHeader-scenarioName]', + headerScenarioStatus: + '[data-test-projectSettings-viewScenarioHeader-scenarioStatus]', + statusToggleSelector: + '[data-test-projectSettings-viewScenarioHeader-scenarioStatus] [data-test-toggle-input]', + deleteScenarioModalTrigger: + '[data-test-projectSettings-viewScenarioHeader-deleteScenarioModalTrigger]', + deleteConfirmTextSelector: + '[data-test-projectSettings-viewScenario-deleteScenarioConfirmText]', + deleteBtnSelector: + '[data-test-projectSettings-viewScenario-deleteScenarioDeleteBtn]', + deleteCancelBtnSelector: + '[data-test-projectSettings-viewScenario-deleteScenarioCancelBtn]', + deleteDefaultScenarioTooltip: + '[data-test-projectSettings-viewScenarioHeader-deleteDefaultScenarioInfo-tooltip]', +}; + +module( + 'Integration | Component | project-settings/view-scenario/header', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + this.owner.register('service:notifications', NotificationsStub); + this.owner.register('service:router', RouterStub); + + // Server mocks + this.server.get('/organizations/:id/members', (schema) => { + const results = schema.organizationMembers.all().models; + return { count: results.length, next: null, previous: null, results }; + }); + + this.server.get('/organizations/:id/me', (schema, req) => + schema.organizationMes.find(`${req.params.id}`)?.toJSON() + ); + + this.server.get('/organizations/:id/users', (schema, req) => + schema.organizationUsers.find(`${req.params.id}`)?.toJSON() + ); + + this.server.get('/v2/projects/:id', (schema, req) => { + return schema.projects.find(req.params.id).toJSON(); + }); + + this.server.get('/v2/files/:id', (schema, req) => { + return schema.files.find(`${req.params.id}`)?.toJSON(); + }); + + this.server.get('/v2/scan_parameter_groups/:id', (schema, req) => + schema.scanParameterGroups.find(req.params.id).toJSON() + ); + + this.server.delete( + '/v2/projects/:projectId/scan_parameter_groups/:id', + function (schema, request) { + const scanParameterGroup = schema.scanParameterGroups.find( + request.params.id + ); + + if (!scanParameterGroup) { + return new Response( + 404, + {}, + { + detail: 'Not found.', + } + ); + } + + scanParameterGroup.destroy(); + return new Response(204, {}, ''); + } + ); + + const store = this.owner.lookup('service:store'); + + // Project Model + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + // Scenario Model - Sets default to true + const scenario = this.server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_active: false, + is_default: true, + }); + + const normalizedScenario = store.normalize('scan-parameter-group', { + ...scenario.toJSON(), + }); + + this.setProperties({ + store, + project: store.push(normalizedProject), + scenario: store.push(normalizedScenario), + }); + }); + + test('it renders with the right properties', async function (assert) { + await render(hbs` + + `); + + assert.dom(selectors.headerRoot).exists(); + + assert + .dom(selectors.headerScenarioName) + .exists() + .containsText('t:scenario:()') + .containsText(this.scenario.name); + + assert + .dom(selectors.headerScenarioStatus) + .exists() + .containsText('t:enabled:()'); + + assert.dom(selectors.statusToggleSelector).exists().isNotChecked(); + + // Tootlip selector for default scenario + assert + .dom(selectors.deleteScenarioModalTrigger) + .exists() + .hasAttribute('disabled'); + }); + + test.each( + 'it deletes a scenario if not default', + [true, false], + async function (assert, is_default) { + this.scenario.set('isDefault', is_default); + + await render( + hbs`` + ); + + if (is_default) { + // Tootlip selector for default scenario + assert + .dom(selectors.deleteScenarioModalTrigger) + .exists() + .hasAttribute('disabled'); + + const deleteDefaultScenarioInfoTooltip = find( + selectors.deleteDefaultScenarioTooltip + ); + + await triggerEvent(deleteDefaultScenarioInfoTooltip, 'mouseenter'); + + assert + .dom('[data-test-ak-tooltip-content]') + .exists() + .containsText('t:dastAutomation.deleteDefaultScenarioInfo:()'); + } else { + const router = this.owner.lookup('service:router'); + + // In DAST Automation route + assert.strictEqual( + router.currentRouteName, + 'authenticated.project.settings.dast-automation-scenario' + ); + + assert + .dom(selectors.deleteScenarioModalTrigger) + .exists() + .doesNotHaveAttribute('disabled'); + + await click(selectors.deleteScenarioModalTrigger); + + assert + .dom(selectors.deleteConfirmTextSelector) + .exists() + .containsText( + t('dastAutomation.scenarioDeleteConfirm', { + scenarioName: this.scenario.name, + }) + ); + + assert + .dom(selectors.deleteCancelBtnSelector) + .exists() + .containsText(t('cancel')); + + assert + .dom(selectors.deleteBtnSelector) + .exists() + .containsText(t('yesDelete')); + + await click(selectors.deleteBtnSelector); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual( + notify.successMsg, + t('dastAutomation.scenarioDeleted', { + scenarioName: this.scenario.name, + }) + ); + + // Navigates to project settings after delete + assert.strictEqual( + router.currentRouteName, + 'authenticated.project.settings' + ); + + assert.strictEqual(router.projectId, this.project.get('id')); + } + } + ); + } +); diff --git a/tests/integration/components/project-settings/view-scenario/index-test.js b/tests/integration/components/project-settings/view-scenario/index-test.js new file mode 100644 index 0000000000..3fe5cf0c3e --- /dev/null +++ b/tests/integration/components/project-settings/view-scenario/index-test.js @@ -0,0 +1,232 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { find, findAll, render, waitFor, waitUntil } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl, t } from 'ember-intl/test-support'; +import Service from '@ember/service'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +// Selectors +const selectors = { + scenarioHeaderRoot: '[data-test-projectSettings-viewScenario-header-root]', + addParamerRoot: '[data-test-projectSettings-viewScenario-addParameterRoot]', + paramListLoader: '[data-test-projectSettings-viewScenario-paramListLoader]', + paramListEmpty: '[data-test-projectSettings-viewScenario-paramListEmpty]', + paramListEmptyIllustration: + '[data-test-projectSettings-viewScenario-paramListEmptyIllustration]', + paramListEmptyHeaderText: + '[data-test-projectSettings-viewScenario-paramListEmptyHeaderText]', + paramListEmptyDescText: + '[data-test-projectSettings-viewScenario-paramListEmptyDescText]', + + // Parameter item selectors + paramItemRoot: '[data-test-projectSettings-viewScenario-parameterItem-root]', + paramName: '[data-test-projectSettings-viewScenario-parameterItem-name]', + paramValueTextField: + '[data-test-projectSettings-viewScenario-parameterItem-valueTextField]', + paramValueTextFieldSecureIcon: + '[data-test-projectSettings-parameterItem-valueTextField-secureIcon]', + paramDeleteBtn: + '[data-test-projectSettings-viewScenario-parameterItem-deleteButton]', + paramDeleteBtnIcon: + '[data-test-projectSettings-viewScenario-parameterItem-deleteButtonIcon]', +}; + +module( + 'Integration | Component | project-settings/view-scenario', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + this.owner.register('service:notifications', NotificationsStub); + const store = this.owner.lookup('service:store'); + + // Project Model + const file = this.server.create('file', 1); + const project = this.server.create('project', { + id: 1, + last_file_id: file.id, + }); + + const normalizedProject = store.normalize('project', { + ...project.toJSON(), + }); + + // Scenario Model - Sets default to true + const scenario = this.server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_active: false, + is_default: true, + }); + + const normalizedScenario = store.normalize('scan-parameter-group', { + ...scenario.toJSON(), + }); + + this.setProperties({ + project: store.push(normalizedProject), + scenario: store.push(normalizedScenario), + }); + + // Server mocks + this.server.get( + '/v2/scan_parameter_groups/:groupId/scan_parameters', + (schema, request) => { + const data = schema.scanParameters + .all() + .models.filter( + (_) => _.scanParameterGroup === request.params.groupId + ); + + let limit = data.length; + let offset = 0; + + if (request.queryParams.limit) { + limit = request.queryParams.limit; + } + + if (request.queryParams.offset) { + offset = request.queryParams.offset; + } + + const retdata = data.slice(offset, offset + limit); + + return { + count: data.length, + next: null, + previous: null, + results: retdata, + }; + } + ); + }); + + test('it renders with loading and empty state', async function (assert) { + render( + hbs`` + ); + + await waitFor(selectors.paramListLoader, { + timeout: 100, + }); + + assert.dom(selectors.paramListLoader).exists(); + + await waitUntil(() => !find(selectors.paramListLoader), { + timeout: 100, + }); + + assert.dom(selectors.scenarioHeaderRoot).exists(); + assert.dom(selectors.addParamerRoot).exists(); + + assert.dom(selectors.paramListEmptyIllustration).exists(); + + assert + .dom(selectors.paramListEmptyHeaderText) + .exists() + .hasText(t('noDataAvailable')); + + assert + .dom(selectors.paramListEmptyDescText) + .exists() + .hasText(t('dastAutomation.noParamaterAvailable')); + }); + + test('it renders with scan parameter list', async function (assert) { + this.server.db.loadData({ + scanParameters: [ + { + name: 'Scan parameter 1', + value: 'Scan parameter 1 Value', + is_secure: false, + scanParameterGroup: 'default', + }, + { + name: 'Scan parameter 2', + value: 'Scan parameter 2 Value', + is_secure: true, + scanParameterGroup: 'default', + }, + ], + }); + + await render( + hbs`` + ); + + const scenarioParams = findAll(selectors.paramItemRoot); + + assert.strictEqual( + scenarioParams.length, + this.server.db.scanParameters.length + ); + + assert.strictEqual( + scenarioParams.length, + this.server.db.scanParameters.length + ); + + // assert first item + const firstItemElement = scenarioParams[0]; + const firstItemRecord = this.server.db.scanParameters[0]; + + assert + .dom(selectors.paramName, firstItemElement) + .exists() + .hasText(firstItemRecord.name); + + assert + .dom(selectors.paramValueTextField, firstItemElement) + .exists() + .hasValue(firstItemRecord.value); + + assert + .dom(selectors.paramValueTextFieldSecureIcon, firstItemElement) + .exists() + .hasClass(/lock-open/); + + assert.dom(selectors.paramDeleteBtn, firstItemElement).exists(); + assert.dom(selectors.paramDeleteBtnIcon, firstItemElement).exists(); + + // assert second item + const secondItemElement = scenarioParams[1]; + const secondItemRecord = this.server.db.scanParameters[1]; + + assert + .dom(selectors.paramName, secondItemElement) + .exists() + .hasText(secondItemRecord.name); + + assert + .dom(selectors.paramValueTextField, secondItemElement) + .exists() + .hasValue(secondItemRecord.value); + + assert + .dom(selectors.paramValueTextFieldSecureIcon, secondItemElement) + .exists() + .doesNotHaveClass(/lock-open/) + .hasClass(/lock/); + + assert.dom(selectors.paramDeleteBtn, secondItemElement).exists(); + assert.dom(selectors.paramDeleteBtnIcon, secondItemElement).exists(); + }); + } +); diff --git a/tests/integration/components/project-settings/view-scenario/parameter-item-test.js b/tests/integration/components/project-settings/view-scenario/parameter-item-test.js new file mode 100644 index 0000000000..86db037cdb --- /dev/null +++ b/tests/integration/components/project-settings/view-scenario/parameter-item-test.js @@ -0,0 +1,368 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { + click, + fillIn, + find, + render, + triggerEvent, + waitFor, + waitUntil, +} from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl, t } from 'ember-intl/test-support'; +import Service from '@ember/service'; + +class NotificationsStub extends Service { + errorMsg = null; + successMsg = null; + + error(msg) { + this.errorMsg = msg; + } + + success(msg) { + this.successMsg = msg; + } +} + +// Selectors +const selectors = { + paramItemRoot: '[data-test-projectSettings-viewScenario-parameterItem-root]', + paramName: '[data-test-projectSettings-viewScenario-parameterItem-name]', + paramValueTextField: + '[data-test-projectSettings-viewScenario-parameterItem-valueTextField]', + paramValueTextFieldToolTip: + '[data-test-projectSettings-viewScenario-parameterItem-valueTextFieldTooltip]', + paramValueTextFieldSecureIcon: + '[data-test-projectSettings-parameterItem-valueTextField-secureIcon]', + paramDeleteBtn: + '[data-test-projectSettings-viewScenario-parameterItem-deleteButton]', + paramDeleteBtnIcon: + '[data-test-projectSettings-viewScenario-parameterItem-deleteButtonIcon]', + paramEditCancelBtn: + '[data-test-projectSettings-parameterItem-valueTextField-editCancelButton]', + paramEditCancelBtnIcon: + '[data-test-projectSettings-parameterItem-valueTextField-editCancelBtnIcon]', + paramEditSaveBtn: + '[data-test-projectSettings-parameterItem-valueTextField-editSaveButton]', + paramEditSaveBtnIcon: + '[data-test-projectSettings-parameterItem-valueTextField-editSaveBtnIcon]', + paramEditSaveLoader: + '[data-test-projectSettings-viewScenario-parameterItem-editSaveLoader]', + maskParameterText: + '[data-test-projectSettings-viewScenario-maskFieldModal-maskParameterText]', + maskSaveBtn: + '[data-test-projectSettings-viewScenario-maskFieldModal-maskSaveBtn]', + maskCancelBtn: + '[data-test-projectSettings-viewScenario-maskFieldModal-maskCancelBtn]', + deleteConfirmText: + '[data-test-projectSettings-viewScenario-deleteParamModal-confirmText]', + deleteInputValueInfo: + '[data-test-projectSettings-viewScenario-deleteParamModal-inputValueInfo]', + deleteInputTypeInfo: + '[data-test-projectSettings-viewScenario-deleteParamModal-inputTypeInfo]', + deleteCancelBtn: + '[data-test-projectSettings-viewScenario-deleteParamModal-deleteCancelBtn]', + deleteConfirmBtn: + '[data-test-projectSettings-viewScenario-deleteParamModal-deleteConfirmBtn]', +}; + +module( + 'Integration | Component | project-settings/view-scenario/parameter-item', + function (hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function () { + this.owner.register('service:notifications', NotificationsStub); + + const store = this.owner.lookup('service:store'); + + // Scenario Model - Sets default to true + const scenario = this.server.create('scan-parameter-group', { + id: 'default', + project: 1, + name: 'Default', + is_active: false, + is_default: true, + }); + + const normalizedScenario = store.normalize('scan-parameter-group', { + ...scenario.toJSON(), + }); + + // Parameter Model + const parameter = this.server.create('scan-parameter', { + id: 1, + scanParameterGroup: 'default', + name: 'Parameter', + value: 'Parameter Value', + }); + + const normalizedParameter = store.normalize('scan-parameter', { + ...parameter.toJSON(), + }); + + this.setProperties({ + scenario: store.push(normalizedScenario), + parameter: store.push(normalizedParameter), + reloadParameterList: () => {}, + }); + }); + + test('it renders', async function (assert) { + await render(hbs` + + `); + + assert.dom(selectors.paramItemRoot).exists(); + assert.dom(selectors.paramName).exists().hasText(this.parameter.name); + + assert + .dom(selectors.paramValueTextField) + .exists() + .hasValue(this.parameter.value); + + assert + .dom(selectors.paramValueTextFieldSecureIcon) + .exists() + .hasClass(this.parameter.isSecure ? /lock/ : /lock-open/); + + assert.dom(selectors.paramDeleteBtn).exists(); + assert.dom(selectors.paramDeleteBtnIcon).exists(); + + await triggerEvent(selectors.paramValueTextFieldToolTip, 'mouseenter'); + + assert + .dom('[data-test-ak-tooltip-content]') + .exists() + .containsText(t('dastAutomation.clickToEdit')); + }); + + test.each( + 'it edits a parameter value', + [true, false], + async function (assert, is_secure) { + const valueToEditTo = 'edited value'; + this.parameter.set('isSecure', is_secure); + + this.server.put( + '/v2/scan_parameter_groups/:groupId/scan_parameters/:id', + function (schema, request) { + const { is_secure, ...rest } = JSON.parse(request.requestBody); + const id = request.params.id; + + // Checks to see if value to edit to is correct + assert.strictEqual( + valueToEditTo, + rest.value, + 'Edits with correct parameter value' + ); + + return schema.scanParameters + .find(id) + .update({ is_secure: is_secure === 'true', ...rest }); + }, + { timing: 150 } + ); + + await render(hbs` + + `); + + assert + .dom(selectors.paramValueTextField) + .exists() + .hasValue(this.parameter.value) + .hasAttribute('type', this.parameter.isSecure ? 'password' : 'text'); + + await triggerEvent(selectors.paramValueTextField, 'focus'); + + assert.dom(selectors.paramEditCancelBtn).exists(); + assert.dom(selectors.paramEditCancelBtnIcon).exists(); + + assert.dom(selectors.paramEditSaveBtn).exists(); + assert.dom(selectors.paramEditSaveBtnIcon).exists(); + + assert + .dom(selectors.paramValueTextField) + + .hasValue(this.parameter.isSecure ? '' : this.parameter.value) + .hasAttribute('type', 'text'); + + await fillIn(selectors.paramValueTextField, valueToEditTo); + + // Cancel resets input value to default and hides edit ctas + await click(selectors.paramEditCancelBtn); + + assert + .dom(selectors.paramValueTextField) + .hasValue(this.parameter.value); + + assert.dom(selectors.paramEditSaveBtn).doesNotExist(); + assert.dom(selectors.paramEditCancelBtn).doesNotExist(); + + // Refill input value field + await triggerEvent(selectors.paramValueTextField, 'focus'); + await fillIn(selectors.paramValueTextField, valueToEditTo); + + // Save triggers edit loader + click(selectors.paramEditSaveBtn); + + await waitFor(selectors.paramEditSaveLoader, { + timeout: 150, + }); + + assert.dom(selectors.paramEditSaveLoader).exists(); + + await waitUntil(() => !find(selectors.paramEditSaveLoader), { + timeout: 150, + }); + + assert.dom(selectors.paramValueTextField).hasValue(valueToEditTo); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual( + notify.successMsg, + t('dastAutomation.paramEditSuccess') + ); + } + ); + + test('it toggles secure field', async function (assert) { + assert.expect(8); + + // Default secure state + const isSecure = false; + this.parameter.set('isSecure', isSecure); + + this.server.put( + '/v2/scan_parameter_groups/:groupId/scan_parameters/:id', + (schema, request) => { + const id = request.params.id; + const { is_secure, ...rest } = JSON.parse(request.requestBody); + + // Sets the value of paramter + this.parameter.set('isSecure', is_secure); + + return schema.scanParameters.find(id).update({ is_secure, ...rest }); + } + ); + + await render(hbs` + + `); + + assert + .dom(selectors.paramValueTextFieldSecureIcon) + .exists() + .hasClass(/lock-open/); + + await click(selectors.paramValueTextFieldSecureIcon); + + assert + .dom(selectors.maskParameterText) + .hasText(t('dastAutomation.maskParameterText')); + + assert.dom(selectors.maskCancelBtn).hasText(t('cancel')); + assert.dom(selectors.maskSaveBtn).hasText(t('yesSecure')); + + await click(selectors.maskSaveBtn); + + assert + .dom(selectors.paramValueTextFieldSecureIcon) + .doesNotHaveClass(/lock-open/) + .hasClass(/lock/); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual( + notify.successMsg, + t('dastAutomation.paramEditSuccess') + ); + }); + + test('it deletes a parameter', async function (assert) { + assert.expect(14); + + // Default secure state + const isSecure = false; + this.parameter.set('isSecure', isSecure); + + this.server.delete( + '/v2/scan_parameter_groups/:groupId/scan_parameters/:id', + () => { + assert.ok(true, 'It deletes successfully'); + + return new Response(204, {}, ''); + } + ); + + await render(hbs` + + `); + + await click(selectors.paramDeleteBtn); + + assert + .dom(selectors.deleteConfirmText) + .exists() + .hasText( + t('dastAutomation.parameterDeleteConfirm', { + scenarioName: this.scenario.name, + }) + ); + + assert + .dom(selectors.deleteInputTypeInfo) + .exists() + .containsText(t('dastAutomation.inputType')) + .containsText(this.parameter.name); + + assert + .dom(selectors.deleteInputValueInfo) + .exists() + .containsText(t('dastAutomation.inputValue')) + .containsText(this.parameter.value); + + assert.dom(selectors.deleteCancelBtn).exists().containsText(t('cancel')); + + assert + .dom(selectors.deleteConfirmBtn) + .exists() + .containsText(t('yesDelete')); + + await click(selectors.deleteCancelBtn); // Closes modal + + await click(selectors.paramDeleteBtn); + await click(selectors.deleteConfirmBtn); + + const notify = this.owner.lookup('service:notifications'); + + assert.strictEqual( + notify.successMsg, + t('dastAutomation.paramDeleteSuccess') + ); + }); + } +); diff --git a/tests/test-utils.js b/tests/test-utils.js index 31c948a2ad..4920798f75 100644 --- a/tests/test-utils.js +++ b/tests/test-utils.js @@ -19,3 +19,17 @@ export function serializer(data, many = false) { return _serialize_object(data); } } + +export function objectifyEncodedReqBody(reqBody = '') { + // Splitting the string into an array of key-value pairs + const pairs = reqBody.split('&'); + + // Converting the array into an object using reduce + const paramsObject = pairs.reduce((acc, pair) => { + const [key, value] = pair.split('='); + acc[key] = decodeURIComponent(value.replace(/\+/g, ' ')); + return acc; + }, {}); + + return paramsObject; +} diff --git a/tests/unit/routes/authenticated/project/settings/dast-automation-scenario.js b/tests/unit/routes/authenticated/project/settings/dast-automation-scenario.js new file mode 100644 index 0000000000..e0aabd2614 --- /dev/null +++ b/tests/unit/routes/authenticated/project/settings/dast-automation-scenario.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Route | authenticated/project/settings/dast-automation-scenario', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + const route = this.owner.lookup( + 'route:authenticated/project/settings/dast-automation-scenario' + ); + + assert.ok(route); + }); + } +); diff --git a/translations/en.json b/translations/en.json index 05a5fe9cd6..920efecf6c 100644 --- a/translations/en.json +++ b/translations/en.json @@ -17,6 +17,7 @@ "active": "active", "activeCapital": "Active", "activeSubscription": "Active Subscription", + "add": "Add", "addCollaborator": "Add Collaborator", "addDomain": "Add Domain", "addTeamMember": "Add Team Member", @@ -66,13 +67,13 @@ "appCenterPipeline": "App Center Build", "appOrS": "app(s)", "appiumFileUploadedSuccessfully": "File Uploaded Successfully", - "appiumScheduledAutomation": "Scheduled Automation", + "appiumScheduledAutomation": "Schedule Automation", "appiumScheduledAutomationSuccessOff": "Scheduled dynamic scan automation turned OFF", "appiumScheduledAutomationSuccessOn": "Scheduled dynamic scan automation turned ON", "appiumScriptInvalid": "Invalid script file. Please upload a valid Appium project", "appiumScripts": "Appium scripts", "appiumScriptsDescription": "As a part of our upcoming automated dynamic scan feature, we are currently supporting scheduled dynamic scans option using Appium scripts. Upload your app's Appium test scripts here as a zip file, dynamic scans would be automatically scheduled for all your future app uploads in this project.", - "appiumScriptsNote": "Scheduled dynamic scans may take upto 24 hours to be completed.", + "appiumScriptsSchedNote": "Scheduled dynamic scans may take upto 24 hours to be completed.", "approvalStatus": "Approval", "approvalRequest": "Approval Request", "approve": "Approve", @@ -146,6 +147,7 @@ "completed": "Completed", "compliantSolution": "Compliant Solution", "confirm": "Confirm", + "confirmation": "Confirmation", "confirmBox": { "removeURL": "Are you sure you want to remove from url filters?", "removeUser": "Are you sure you want to remove this user from team?", @@ -193,6 +195,33 @@ "cvssMetrics": "CVSS Metrics", "cwe": "CWE", "cweExpansion": "Common Weakness Enumeration", + "dastAutomation": { + "dastAutomationScenario": "DAST Automation Scenario", + "addScenario": "Add Scenario", + "parameterAdded": "Parameter added successfully", + "paramEditSuccess": "Parameter edited successfully", + "paramDeleteSuccess": "Parameter deleted successfully", + "scenarioAdded": "Scenario added", + "enterScenarioName": "Enter scenario name", + "automationScenarios": "Automation Scenarios", + "scenarioListDesc": " Create multiple scenarios and configure them with the necessary settings for utilization in the automation of Dynamic Application Security Testing (DAST)", + "deleteDefaultScenarioInfo": "You cannot delete a default scenario.", + "inputTypeColumnHeaderInfo": "Example values for Input Type are
Username, Password or Email ID etc...", + "paramTypeDupText": "Input type - \"{type}\" already exists. Kindly use a different input type.", + "inputValue": "Input Value", + "inputType": "Input Type", + "enterType": "Enter Type", + "noScenariosFound": "No scenarios found.", + "statusColumnToggleInfo": "Use this toggle to Activate/Deactivate a scenario. Only Active scenarios will be used for executing DAST automation.", + "scenarioStatusUpdated": "Scenario status updated successfully", + "scenarioDeleted": "Scenario - \"{scenarioName}\" deleted", + "scenarioDeleteConfirm": "Would you like to delete Scenario - {scenarioName}?", + "clickToEdit": "Click to edit value", + "unmaskNotAllowed": "You cannot unmask a masked field value.", + "maskParameterText": "Marking this value as secure will irreversibly mask the value. Are you sure you want to proceed?", + "parameterDeleteConfirm": "Would you like to delete this config under Scenario - {scenarioName}?", + "noParamaterAvailable": "No input parameters exist for this scenario. Use the Input Type and Input Value fields above to create the parameters for this scenario." + }, "date": "DATE", "dateCreated": "Date Created", "dateUpdated": "Date Updated", @@ -246,6 +275,7 @@ "dynamicScanStop": "Stop Dynamic Scan", "dynamicScanText": "Please refresh the page if the scan doesn't start in 1-2 mins", "dynamicShutDown": "Shutdown Device", + "dynScanAutoSchedNote": "Once you have created a scenario and added the relevant input parameters, turn on this toggle to schedule Automated Dynamic Scans. It may take up to 24hrs for the Automated Dynamic Scans to be completed.", "edit": "Edit", "editAccess": "Edit Access", "editName": "Edit Name", @@ -672,6 +702,7 @@ "noClientUploads": "This client has not uploaded any apps", "noClients": "No clients", "noCollaborators": "No Collaborators", + "noDataAvailable": "No Data Available", "noDeviceAvailable": "No Device Available", "noFileSelected": "No file selected", "noFiles": "No Files", @@ -698,6 +729,7 @@ "none": "None", "nonCompliantCodeExample": "Noncompliant Code Example", "notAvailable": "Not Available", + "notFound": "Not Found", "notScanned": "Not Scanned", "note": "Note", @@ -1108,6 +1140,7 @@ "scanTypes": "Scan Types", "scanning": "Scanning", "scansLeft": " Scan Left", + "scenario": "Scenario", "scheduleDynamicscan": "Schedule Automated Dynamic Scan", "scheduleDynamicscanDesc": "Schedule an automated dynamic scan for this file using the Appium script provided in the project settings.", "scheduleDynamicscanSuccess": "Automated dynamic scan scheduled successfully", @@ -1305,6 +1338,8 @@ "yearWithNum": "{numYears, plural, =0 {0 Year} =1 {1 Year} other {# Years}}", "yearly": "Yearly", "yes": "Yes", + "yesDelete": "Yes, Delete", + "yesSecure": "Yes, Secure", "youCanCreateTeam": "you can create a new team by", "youWillBeChargedOn": "You will be charged on", "yourPlan": "Your Plan" diff --git a/translations/ja.json b/translations/ja.json index 4d79d0834a..5545a18c85 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -17,6 +17,7 @@ "active": "active", "activeCapital": "Active", "activeSubscription": "有効なサブスクリプション", + "add": "Add", "addCollaborator": "Add Collaborator", "addDomain": "Add Domain", "addTeamMember": "Add Team Member", @@ -66,13 +67,13 @@ "appCenterPipeline": "App Center Build", "appOrS": "アプリ", "appiumFileUploadedSuccessfully": "File Uploaded Successfully", - "appiumScheduledAutomation": "Scheduled
Automation", + "appiumScheduledAutomation": "Schedule Automation", "appiumScheduledAutomationSuccessOff": "Scheduled dynamic scan automation turned OFF", "appiumScheduledAutomationSuccessOn": "Scheduled dynamic scan automation turned ON", "appiumScriptInvalid": "Invalid script file. Please upload a valid Appium project", "appiumScripts": "Appium scripts", "appiumScriptsDescription": "As a part of our upcoming automated dynamic scan feature, we are currently supporting scheduled dynamic scans option using Appium scripts. Upload your app's Appium test scripts here as a zip file, dynamic scans would be automatically scheduled for all your future app uploads in this project.", - "appiumScriptsNote": "Scheduled dynamic scans may take upto 24 hours to be completed.", + "appiumScriptsSchedNote": "Scheduled dynamic scans may take upto 24 hours to be completed.", "approvalStatus": "Approval", "approvalRequest": "Approval Request", "approve": "Approve", @@ -146,6 +147,7 @@ "completed": "完了しました", "compliantSolution": "Compliant Solution", "confirm": "Confirm", + "confirmation": "Confirmation", "confirmBox": { "removeURL": "URLフィルタから削除してもよろしいですか?", "removeUser": "Are you sure you want to remove this user from team?", @@ -193,6 +195,33 @@ "cvssMetrics": "CVSS Metrics", "cwe": "CWE", "cweExpansion": "Common Weakness Enumeration", + "dastAutomation": { + "dastAutomationScenario": "DAST Automation Scenario", + "addScenario": "Add Scenario", + "parameterAdded": "Parameter added successfully", + "paramEditSuccess": "Parameter edited successfully", + "paramDeleteSuccess": "Parameter deleted successfully", + "scenarioAdded": "Scenario added", + "enterScenarioName": "Enter scenario name", + "automationScenarios": "Automation Scenarios", + "scenarioListDesc": "Create multiple scenarios and configure them with the necessary settings for utilization in the automation of Dynamic Application Security Testing (DAST)", + "deleteDefaultScenarioInfo": "You cannot delete a default scenario.", + "inputTypeColumnHeaderInfo": "Example values for Input Type are
Username, Password or Email ID etc...", + "paramTypeDupText": "Input type - \"{type}\" already exists. Kindly use a different input type.", + "inputValue": "Input Value", + "inputType": "Input Type", + "enterType": "Enter Type", + "noScenariosFound": "No scenarios found.", + "statusColumnToggleInfo": "Use this toggle to Activate/Deactivate a scenario. Only Active scenarios will be used for executing DAST automation.", + "scenarioStatusUpdated": "Scenario status updated successfully", + "scenarioDeleted": "Scenario - \"{scenarioName}\" deleted", + "scenarioDeleteConfirm": "Would you like to delete Scenario - {scenarioName}?", + "clickToEdit": "Click to edit value", + "unmaskNotAllowed": "You cannot unmask a masked field value.", + "maskParameterText": "Marking this value as secure will irreversibly mask the value. Are you sure you want to proceed?", + "parameterDeleteConfirm": "Would you like to delete this config under Scenario - {scenarioName}?", + "noParamaterAvailable": "No input parameters exist for this scenario. Use the Input Type and Input Value fields above to create the parameters for this scenario." + }, "date": "日付", "dateCreated": "作成日", "dateUpdated": "更新日", @@ -246,6 +275,7 @@ "dynamicScanStop": "動的診断を停止", "dynamicScanText": "Please refresh the page if the scan doesn't start in 1-2 mins", "dynamicShutDown": "動的診断を終了", + "dynScanAutoSchedNote": "Once you have created a scenario and added the relevant input parameters, turn on this toggle to schedule Automated Dynamic Scans. It may take up to 24hrs for the Automated Dynamic Scans to be completed.", "edit": "Edit", "editAccess": "Edit Access", "editName": "Edit Name", @@ -672,6 +702,7 @@ "noClientUploads": "This client has not uploaded any apps", "noClients": "No clients", "noCollaborators": "No Collaborators", + "noDataAvailable": "No Data Available", "noDeviceAvailable": "使用可能なデバイスがありません", "noFileSelected": "No file selected", "noFiles": "ファイルがありません", @@ -1108,6 +1139,7 @@ "scanTypes": "Scan Types", "scanning": "診断中", "scansLeft": "未実施の診断", + "scenario": "Scenario", "scheduleDynamicscan": "Schedule Automated Dynamic Scan", "scheduleDynamicscanDesc": "Schedule an automated dynamic scan for this file using the Appium script provided in the project settings.", "scheduleDynamicscanSuccess": "Automated dynamic scan scheduled successfully", @@ -1305,6 +1337,8 @@ "yearWithNum": "{numYears, plural, =0 {0年} =1 {1年} other {#年}}", "yearly": "1年", "yes": "はい", + "yesDelete": "Yes, Delete", + "yesSecure": "Yes, Secure", "youCanCreateTeam": "you can create a new team by", "youWillBeChargedOn": "次のとおり請求されます:", "yourPlan": "Your Plan" diff --git a/types/ak-svg.d.ts b/types/ak-svg.d.ts index 1bc8976dfc..98c4ea7ab5 100644 --- a/types/ak-svg.d.ts +++ b/types/ak-svg.d.ts @@ -31,6 +31,7 @@ export enum AkSvgComponentInvocationByNames { MainLoaderImage3, AppstoreLogo, PlaystoreLogo, + NoParameterData, } export enum AkSvgComponentInvocationByPaths {