From 2ab5d790d354ef113921363da65d8762aac1b943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 12 Mar 2019 10:30:06 +0100 Subject: [PATCH 01/55] Load alert data from store for TaskDialog Avoid using the newTaskSettings command to load the alerts and default alert id. Instead load them from the store. --- gsa/src/web/pages/tasks/component.js | 64 +++++++++++++++++++--------- 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/gsa/src/web/pages/tasks/component.js b/gsa/src/web/pages/tasks/component.js index efdf2503f3..a1b70ef133 100644 --- a/gsa/src/web/pages/tasks/component.js +++ b/gsa/src/web/pages/tasks/component.js @@ -28,7 +28,7 @@ import {NO_VALUE} from 'gmp/parser'; import {first, forEach, map} from 'gmp/utils/array'; import {isArray, isDefined} from 'gmp/utils/identity'; -import {includesId, selectSaveId} from 'gmp/utils/id'; +import {selectSaveId} from 'gmp/utils/id'; import date from 'gmp/models/date'; @@ -41,8 +41,16 @@ import { import {OPENVAS_DEFAULT_SCANNER_ID} from 'gmp/models/scanner'; +import { + loadEntities as loadAlerts, + selector as alertSelector, +} from 'web/store/entities/alerts'; + import {getTimezone} from 'web/store/usersettings/selectors'; +import {loadUserSettingDefaults} from 'web/store/usersettings/defaults/actions'; +import {getUserSettingsDefaults} from 'web/store/usersettings/defaults/selectors'; + import compose from 'web/utils/compose'; import PropTypes from 'web/utils/proptypes'; import withCapabilities from 'web/utils/withCapabilities'; @@ -148,6 +156,10 @@ class TaskComponent extends React.Component { this.handleInteraction = this.handleInteraction.bind(this); } + componentDidMount() { + this.props.loadUserSettingsDefaults(); + } + handleInteraction() { const {onInteraction} = this.props; if (isDefined(onInteraction)) { @@ -198,14 +210,10 @@ class TaskComponent extends React.Component { handleAlertCreated(resp) { const {data} = resp; - const {alert_ids} = this.state; - const {gmp} = this.props; - gmp.alerts.getAll().then(response => { - const {data: alerts} = response; + this.props.loadAlerts(); - this.setState({alerts, alert_ids: [...alert_ids, data.id]}); - }); + this.setState(({alert_ids}) => ({alert_ids: [data.id, ...alert_ids]})); } handleScheduleCreated(resp) { @@ -299,10 +307,12 @@ class TaskComponent extends React.Component { openStandardTaskDialog(task) { const {capabilities, gmp} = this.props; + this.props.loadAlerts(); + if (isDefined(task)) { gmp.task.editTaskSettings(task).then(response => { const settings = response.data; - const {targets, scan_configs, alerts, scanners, schedules} = settings; + const {targets, scan_configs, scanners, schedules} = settings; log.debug('Loaded edit task dialog settings', task, settings); @@ -340,7 +350,6 @@ class TaskComponent extends React.Component { taskDialogVisible: true, ...data, alert_ids: map(task.alerts, alert => alert.id), - alerts, alterable: task.alterable, apply_overrides: task.apply_overrides, auto_delete: task.auto_delete, @@ -365,17 +374,17 @@ class TaskComponent extends React.Component { }); }); } else { + const {defaultAlertId} = this.props; + gmp.task.newTaskSettings().then(response => { const settings = response.data; let { schedule_id, - alert_id, target_id, targets, scanner_id = OPENVAS_DEFAULT_SCANNER_ID, scan_configs, config_id = FULL_AND_FAST_SCAN_CONFIG_ID, - alerts, scanners, schedules, tags, @@ -391,14 +400,11 @@ class TaskComponent extends React.Component { schedule_id = selectSaveId(schedules, schedule_id, UNSET_VALUE); - alert_id = includesId(alerts, alert_id) ? alert_id : undefined; - - const alert_ids = isDefined(alert_id) ? [alert_id] : []; + const alert_ids = isDefined(defaultAlertId) ? [defaultAlertId] : []; this.setState({ taskDialogVisible: true, alert_ids, - alerts, alterable: undefined, apply_overrides: undefined, auto_delete: undefined, @@ -621,6 +627,7 @@ class TaskComponent extends React.Component { render() { const { + alerts, children, onCloned, onCloneError, @@ -639,7 +646,6 @@ class TaskComponent extends React.Component { advancedTaskWizardVisible, alert_id, alert_ids, - alerts, alterable, apply_overrides, auto_delete, @@ -867,9 +873,13 @@ class TaskComponent extends React.Component { } TaskComponent.propTypes = { + alerts: PropTypes.arrayOf(PropTypes.model), capabilities: PropTypes.capabilities.isRequired, children: PropTypes.func.isRequired, + defaultAlertId: PropTypes.id, gmp: PropTypes.gmp.isRequired, + loadAlerts: PropTypes.func.isRequired, + loadUserSettingsDefaults: PropTypes.func.isRequired, timezone: PropTypes.string.isRequired, onAdvancedTaskWizardError: PropTypes.func, onAdvancedTaskWizardSaved: PropTypes.func, @@ -902,10 +912,26 @@ TaskComponent.propTypes = { onTaskWizardSaved: PropTypes.func, }; +const mapStateToProps = rootState => { + const alertSel = alertSelector(rootState); + const userDefaults = getUserSettingsDefaults(rootState); + return { + timezone: getTimezone(rootState), + alerts: alertSel.getEntities(), + defaultAlertId: userDefaults.getValueByName('defaultalert'), + }; +}; + +const mapDispatchToProp = (dispatch, {gmp}) => ({ + loadAlerts: () => dispatch(loadAlerts(gmp)()), + loadUserSettingsDefaults: () => dispatch(loadUserSettingDefaults(gmp)()), +}); + export default compose( withGmp, withCapabilities, - connect(rootState => ({ - timezone: getTimezone(rootState), - })), + connect( + mapStateToProps, + mapDispatchToProp, + ), )(TaskComponent); From 0a5c59344631f50e3bd4e798be98e2264f65b2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 12 Mar 2019 15:07:41 +0100 Subject: [PATCH 02/55] Use undefined for user setting if it is set to no value Either store the actual value of the setting or use undefined if it isn't set. There should only be one meaning of not set and that's using undefined. --- gsa/src/gmp/commands/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsa/src/gmp/commands/users.js b/gsa/src/gmp/commands/users.js index b244a76207..dd7891e651 100644 --- a/gsa/src/gmp/commands/users.js +++ b/gsa/src/gmp/commands/users.js @@ -106,7 +106,7 @@ class UserCommand extends EntityCommand { id: setting._id, comment: setting.comment === '(null)' ? undefined : setting.comment, name: setting.name, - value: setting.value, + value: setting.value === '0' ? undefined : setting.value, }; }); return response.setData(settings); From 1226858b3d69d0656330388f6606ba854a476e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 12 Mar 2019 15:09:51 +0100 Subject: [PATCH 03/55] Load targets for TargetDialog from the redux store --- gsa/src/web/pages/tasks/component.js | 33 +++++++++++++++------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/gsa/src/web/pages/tasks/component.js b/gsa/src/web/pages/tasks/component.js index a1b70ef133..1ffa8b632e 100644 --- a/gsa/src/web/pages/tasks/component.js +++ b/gsa/src/web/pages/tasks/component.js @@ -46,6 +46,11 @@ import { selector as alertSelector, } from 'web/store/entities/alerts'; +import { + loadEntities as loadTargets, + selector as targetSelector, +} from 'web/store/entities/targets'; + import {getTimezone} from 'web/store/usersettings/selectors'; import {loadUserSettingDefaults} from 'web/store/usersettings/defaults/actions'; @@ -232,15 +237,10 @@ class TaskComponent extends React.Component { handleTargetCreated(resp) { const {data} = resp; - const {gmp} = this.props; - gmp.targets.getAll().then(response => { - const {data: alltargets} = response; + this.props.loadTargets(); - log.debug('adding target to task dialog', alltargets, data.id); - - this.setState({targets: alltargets, target_id: data.id}); - }); + this.setState({target_id: data.id}); } openContainerTaskDialog(task) { @@ -308,6 +308,7 @@ class TaskComponent extends React.Component { const {capabilities, gmp} = this.props; this.props.loadAlerts(); + this.props.loadTargets(); if (isDefined(task)) { gmp.task.editTaskSettings(task).then(response => { @@ -374,14 +375,12 @@ class TaskComponent extends React.Component { }); }); } else { - const {defaultAlertId} = this.props; + const {defaultAlertId, defaultTargetId} = this.props; gmp.task.newTaskSettings().then(response => { const settings = response.data; let { schedule_id, - target_id, - targets, scanner_id = OPENVAS_DEFAULT_SCANNER_ID, scan_configs, config_id = FULL_AND_FAST_SCAN_CONFIG_ID, @@ -396,8 +395,6 @@ class TaskComponent extends React.Component { scanner_id = selectSaveId(scanners, scanner_id); - target_id = selectSaveId(targets, target_id); - schedule_id = selectSaveId(schedules, schedule_id, UNSET_VALUE); const alert_ids = isDefined(defaultAlertId) ? [defaultAlertId] : []; @@ -427,8 +424,7 @@ class TaskComponent extends React.Component { source_iface: undefined, tag_id: first(tags).id, tags, - target_id, - targets, + target_id: defaultTargetId, task: undefined, title: _('New Task'), }); @@ -628,6 +624,7 @@ class TaskComponent extends React.Component { render() { const { alerts, + targets, children, onCloned, onCloneError, @@ -685,7 +682,6 @@ class TaskComponent extends React.Component { tags, target_id, target_hosts, - targets, task_id, task_name, task, @@ -877,9 +873,12 @@ TaskComponent.propTypes = { capabilities: PropTypes.capabilities.isRequired, children: PropTypes.func.isRequired, defaultAlertId: PropTypes.id, + defaultTargetId: PropTypes.id, gmp: PropTypes.gmp.isRequired, loadAlerts: PropTypes.func.isRequired, + loadTargets: PropTypes.func.isRequired, loadUserSettingsDefaults: PropTypes.func.isRequired, + targets: PropTypes.arrayOf(PropTypes.model), timezone: PropTypes.string.isRequired, onAdvancedTaskWizardError: PropTypes.func, onAdvancedTaskWizardSaved: PropTypes.func, @@ -915,15 +914,19 @@ TaskComponent.propTypes = { const mapStateToProps = rootState => { const alertSel = alertSelector(rootState); const userDefaults = getUserSettingsDefaults(rootState); + const targetSel = targetSelector(rootState); return { timezone: getTimezone(rootState), alerts: alertSel.getEntities(), defaultAlertId: userDefaults.getValueByName('defaultalert'), + defaultTargetId: userDefaults.getValueByName('defaulttarget'), + targets: targetSel.getEntities(), }; }; const mapDispatchToProp = (dispatch, {gmp}) => ({ loadAlerts: () => dispatch(loadAlerts(gmp)()), + loadTargets: () => dispatch(loadTargets(gmp)()), loadUserSettingsDefaults: () => dispatch(loadUserSettingDefaults(gmp)()), }); From a9a493d3b535fe70b8af6dd779f867a576f4ce29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 12 Mar 2019 15:12:17 +0100 Subject: [PATCH 04/55] Update session time when user opens a sub dialog Pass onInteraction handler down to other component in TaskComponent. This will extend the user session when opening a sub dialog e.g. to create a new alert. --- gsa/src/web/pages/tasks/component.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gsa/src/web/pages/tasks/component.js b/gsa/src/web/pages/tasks/component.js index 1ffa8b632e..18cc556a24 100644 --- a/gsa/src/web/pages/tasks/component.js +++ b/gsa/src/web/pages/tasks/component.js @@ -724,12 +724,19 @@ class TaskComponent extends React.Component { })} {taskDialogVisible && ( - + {({create: createtarget}) => ( - + {({create: createalert}) => ( {({create: createschedule}) => ( Date: Thu, 14 Mar 2019 09:45:24 +0100 Subject: [PATCH 05/55] Load Schedules from redux store in TaskDialog Don't use the schedules from the newTaskSettings request. Instead load the Schedules from the redux store. --- gsa/src/web/pages/tasks/component.js | 37 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/gsa/src/web/pages/tasks/component.js b/gsa/src/web/pages/tasks/component.js index 18cc556a24..58c8bc08c1 100644 --- a/gsa/src/web/pages/tasks/component.js +++ b/gsa/src/web/pages/tasks/component.js @@ -46,6 +46,11 @@ import { selector as alertSelector, } from 'web/store/entities/alerts'; +import { + loadEntities as loadSchedules, + selector as scheduleSelector, +} from 'web/store/entities/schedules'; + import { loadEntities as loadTargets, selector as targetSelector, @@ -223,16 +228,10 @@ class TaskComponent extends React.Component { handleScheduleCreated(resp) { const {data} = resp; - const {gmp} = this.props; - return gmp.schedules.getAll().then(response => { - const {data: schedules} = response; + this.props.loadSchedules(); - this.setState({ - schedules, - schedule_id: data.id, - }); - }); + this.setState({schedule_id: data.id}); } handleTargetCreated(resp) { @@ -308,12 +307,13 @@ class TaskComponent extends React.Component { const {capabilities, gmp} = this.props; this.props.loadAlerts(); + this.props.loadSchedules(); this.props.loadTargets(); if (isDefined(task)) { gmp.task.editTaskSettings(task).then(response => { const settings = response.data; - const {targets, scan_configs, scanners, schedules} = settings; + const {targets, scan_configs, scanners} = settings; log.debug('Loaded edit task dialog settings', task, settings); @@ -367,7 +367,6 @@ class TaskComponent extends React.Component { scanners, schedule_id, schedule_periods, - schedules, source_iface: task.source_iface, targets, task, @@ -375,17 +374,15 @@ class TaskComponent extends React.Component { }); }); } else { - const {defaultAlertId, defaultTargetId} = this.props; + const {defaultAlertId, defaultScheduleId, defaultTargetId} = this.props; gmp.task.newTaskSettings().then(response => { const settings = response.data; let { - schedule_id, scanner_id = OPENVAS_DEFAULT_SCANNER_ID, scan_configs, config_id = FULL_AND_FAST_SCAN_CONFIG_ID, scanners, - schedules, tags, } = settings; @@ -395,8 +392,6 @@ class TaskComponent extends React.Component { scanner_id = selectSaveId(scanners, scanner_id); - schedule_id = selectSaveId(schedules, schedule_id, UNSET_VALUE); - const alert_ids = isDefined(defaultAlertId) ? [defaultAlertId] : []; this.setState({ @@ -418,9 +413,8 @@ class TaskComponent extends React.Component { scan_configs: sorted_scan_configs, scanners, scanner_id, - schedule_id, + schedule_id: defaultScheduleId, schedule_periods: undefined, - schedules, source_iface: undefined, tag_id: first(tags).id, tags, @@ -624,6 +618,7 @@ class TaskComponent extends React.Component { render() { const { alerts, + schedules, targets, children, onCloned, @@ -669,7 +664,6 @@ class TaskComponent extends React.Component { scanners, schedule_id, schedule_periods, - schedules, slave_id, source_iface, ssh_credential, @@ -880,11 +874,14 @@ TaskComponent.propTypes = { capabilities: PropTypes.capabilities.isRequired, children: PropTypes.func.isRequired, defaultAlertId: PropTypes.id, + defaultScheduleId: PropTypes.id, defaultTargetId: PropTypes.id, gmp: PropTypes.gmp.isRequired, loadAlerts: PropTypes.func.isRequired, + loadSchedules: PropTypes.func.isRequired, loadTargets: PropTypes.func.isRequired, loadUserSettingsDefaults: PropTypes.func.isRequired, + schedules: PropTypes.arrayOf(PropTypes.model), targets: PropTypes.arrayOf(PropTypes.model), timezone: PropTypes.string.isRequired, onAdvancedTaskWizardError: PropTypes.func, @@ -921,18 +918,22 @@ TaskComponent.propTypes = { const mapStateToProps = rootState => { const alertSel = alertSelector(rootState); const userDefaults = getUserSettingsDefaults(rootState); + const scheduleSel = scheduleSelector(rootState); const targetSel = targetSelector(rootState); return { timezone: getTimezone(rootState), alerts: alertSel.getEntities(), defaultAlertId: userDefaults.getValueByName('defaultalert'), + defaultScheduleId: userDefaults.getValueByName('defaultschedule'), defaultTargetId: userDefaults.getValueByName('defaulttarget'), + schedules: scheduleSel.getEntities(), targets: targetSel.getEntities(), }; }; const mapDispatchToProp = (dispatch, {gmp}) => ({ loadAlerts: () => dispatch(loadAlerts(gmp)()), + loadSchedules: () => dispatch(loadSchedules(gmp)()), loadTargets: () => dispatch(loadTargets(gmp)()), loadUserSettingsDefaults: () => dispatch(loadUserSettingDefaults(gmp)()), }); From b42a402e8ff5b69bbf1bc69cf52d8367a807e1e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 15 Mar 2019 10:51:22 +0100 Subject: [PATCH 06/55] Don't allow to create container task via create_task command The create_container_task command is envisaged for creating container tasks. --- gsad/src/gsad_gmp.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gsad/src/gsad_gmp.c b/gsad/src/gsad_gmp.c index f493e41107..dac7971e88 100644 --- a/gsad/src/gsad_gmp.c +++ b/gsad/src/gsad_gmp.c @@ -2955,6 +2955,13 @@ create_task_gmp (gvm_connection_t *connection, credentials_t *credentials, CHECK_VARIABLE_INVALID (scanner_id, "Create Task"); CHECK_VARIABLE_INVALID (schedule_id, "Create Task"); + if (str_equal (target_id, "0")) + { + /* Don't allow to create container task via create_task */ + return message_invalid (connection, credentials, params, response_data, + "Given target_id was invalid", "Create Task"); + } + if (params_given (params, "schedule_periods")) { CHECK_VARIABLE_INVALID (schedule_periods, "Create Task"); From 23bd1d1582620df8076cd8be5bacfaadc45e564e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 15 Mar 2019 14:35:10 +0100 Subject: [PATCH 07/55] Extract testing default model methods into an own function Allow to test the default model methods seperately from the model properties. --- gsa/src/gmp/models/testing.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsa/src/gmp/models/testing.js b/gsa/src/gmp/models/testing.js index 298e87a6f4..e60c4d13cf 100644 --- a/gsa/src/gmp/models/testing.js +++ b/gsa/src/gmp/models/testing.js @@ -44,7 +44,7 @@ const testNvtId = modelClass => { }); }; -const testModelProperties = (modelClass, type) => { +export const testModelProperties = (modelClass, type) => { describe(`${type} Model tests`, () => { test('end_time is parsed correctly', () => { const elem = { @@ -176,7 +176,9 @@ const testModelProperties = (modelClass, type) => { expect(() => (model.id = 'bar')).toThrow(); }); }); +}; +export const testModelMethods = (modelClass, type) => { describe(`${type} Model methods tests`, () => { test('isInUse() should return correct true/false', () => { const model1 = new modelClass({in_use: '1'}); @@ -222,11 +224,13 @@ const testModelProperties = (modelClass, type) => { export const testModel = (modelClass, type) => { testModelProperties(modelClass, type); + testModelMethods(modelClass, type); testId(modelClass); }; export const testNvtModel = modelClass => { testModelProperties(modelClass, 'nvt'); + testModelMethods(modelClass, 'nvt'); testNvtId(modelClass); }; From 88c58a3140856fa176abfaf5f1aeaf1dad74081f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 15 Mar 2019 14:36:04 +0100 Subject: [PATCH 08/55] Parse Task host ordering Only set known and allowed values for host ordering. --- gsa/src/gmp/models/task.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gsa/src/gmp/models/task.js b/gsa/src/gmp/models/task.js index e7394abc1e..ba4ac24666 100644 --- a/gsa/src/gmp/models/task.js +++ b/gsa/src/gmp/models/task.js @@ -42,6 +42,10 @@ export const AUTO_DELETE_KEEP = 'keep'; export const AUTO_DELETE_NO = 'no'; export const AUTO_DELETE_DEFAULT_VALUE = 5; +export const HOST_ORDERING_SEQUENTIAL = 'sequential'; +export const HOST_ORDERING_RANDOM = 'random'; +export const HOST_ORDERING_REVERSE = 'reverse'; + export const TASK_STATUS = { running: 'Running', stoprequested: 'Stop Requested', @@ -235,6 +239,14 @@ class Task extends Model { elem.average_duration = parseDuration(elem.average_duration); } + if ( + elem.host_ordering !== HOST_ORDERING_RANDOM && + elem.host_ordering !== HOST_ORDERING_REVERSE && + elem.host_ordering !== HOST_ORDERING_SEQUENTIAL + ) { + delete elem.host_ordering; + } + return elem; } } From f74a9520a4fb9c47d1cbf2acaf6291ffef5976b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 15 Mar 2019 14:46:23 +0100 Subject: [PATCH 09/55] Add tests for Task model --- gsa/src/gmp/models/__tests__/task.js | 218 +++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 gsa/src/gmp/models/__tests__/task.js diff --git a/gsa/src/gmp/models/__tests__/task.js b/gsa/src/gmp/models/__tests__/task.js new file mode 100644 index 0000000000..752f928d6c --- /dev/null +++ b/gsa/src/gmp/models/__tests__/task.js @@ -0,0 +1,218 @@ +/* Copyright (C) 2018-2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import 'core-js/fn/object/entries'; + +import Task, { + HOST_ORDERING_RANDOM, + HOST_ORDERING_REVERSE, + HOST_ORDERING_SEQUENTIAL, + TASK_STATUS, +} from 'gmp/models/task'; +import {testModelProperties} from '../testing'; + +testModelProperties(Task, 'task'); + +describe('Task model tests', () => { + test('should parse undefined host_ordering', () => { + const obj = {host_ordering: undefined}; + const task = new Task(obj); + expect(task.host_ordering).toBeUndefined(); + }); + + test('should parse unkonw host_ordering as undefined', () => { + const obj = {host_ordering: 'foo'}; + const task = new Task(obj); + expect(task.host_ordering).toBeUndefined(); + }); + + test('should parse known host_ordering', () => { + let obj = {host_ordering: HOST_ORDERING_RANDOM}; + let task = new Task(obj); + expect(task.host_ordering).toEqual(HOST_ORDERING_RANDOM); + + obj = {host_ordering: HOST_ORDERING_REVERSE}; + task = new Task(obj); + expect(task.host_ordering).toEqual(HOST_ORDERING_REVERSE); + + obj = {host_ordering: HOST_ORDERING_SEQUENTIAL}; + task = new Task(obj); + expect(task.host_ordering).toEqual(HOST_ORDERING_SEQUENTIAL); + }); +}); + +describe(`Task Model methods tests`, () => { + test('isInUse() should return correct true/false', () => { + const task1 = new Task({in_use: '1'}); + const task2 = new Task({in_use: '0'}); + + expect(task1.isInUse()).toBe(true); + expect(task2.isInUse()).toBe(false); + }); + + test('isInTrash() should return correct true/false', () => { + const task1 = new Task({trash: '1'}); + const task2 = new Task({trash: '0'}); + + expect(task1.isInTrash()).toBe(true); + expect(task2.isInTrash()).toBe(false); + }); + + test('isWritable() should return correct true/false', () => { + const task1 = new Task({writable: '1'}); + const task2 = new Task({writable: '0'}); + + expect(task1.isWritable()).toBe(true); + expect(task2.isWritable()).toBe(false); + }); + + test('isOrphan() should return correct true/false', () => { + const task1 = new Task({orphan: '1'}); + const task2 = new Task({orphan: '0'}); + + expect(task1.isOrphan()).toBe(true); + expect(task2.isOrphan()).toBe(false); + }); + + test('should be a container if target_id is not set', () => { + const task1 = new Task({}); + const task2 = new Task({target: {_id: 'foo'}}); + + expect(task1.isContainer()).toEqual(true); + expect(task2.isContainer()).toEqual(false); + }); + + test('should use status for isActive', () => { + const statusList = { + [TASK_STATUS.running]: true, + [TASK_STATUS.stoprequested]: true, + [TASK_STATUS.deleterequested]: true, + [TASK_STATUS.ultimatedeleterequested]: true, + [TASK_STATUS.resumerequested]: true, + [TASK_STATUS.requested]: true, + [TASK_STATUS.stopped]: false, + [TASK_STATUS.new]: false, + [TASK_STATUS.interrupted]: false, + [TASK_STATUS.container]: false, + [TASK_STATUS.uploading]: false, + [TASK_STATUS.done]: false, + }; + + for (const [status, exp] of Object.entries(statusList)) { + const task = new Task({status}); + expect(task.isActive()).toEqual(exp); + } + }); + + test('should use status for isRunning', () => { + const statusList = { + [TASK_STATUS.running]: true, + [TASK_STATUS.stoprequested]: false, + [TASK_STATUS.deleterequested]: false, + [TASK_STATUS.ultimatedeleterequested]: false, + [TASK_STATUS.resumerequested]: false, + [TASK_STATUS.requested]: false, + [TASK_STATUS.stopped]: false, + [TASK_STATUS.new]: false, + [TASK_STATUS.interrupted]: false, + [TASK_STATUS.container]: false, + [TASK_STATUS.uploading]: false, + [TASK_STATUS.done]: false, + }; + + for (const [status, exp] of Object.entries(statusList)) { + const task = new Task({status}); + expect(task.isRunning()).toEqual(exp); + } + }); + + test('should use status for isStopped', () => { + const statusList = { + [TASK_STATUS.running]: false, + [TASK_STATUS.stoprequested]: false, + [TASK_STATUS.deleterequested]: false, + [TASK_STATUS.ultimatedeleterequested]: false, + [TASK_STATUS.resumerequested]: false, + [TASK_STATUS.requested]: false, + [TASK_STATUS.stopped]: true, + [TASK_STATUS.new]: false, + [TASK_STATUS.interrupted]: false, + [TASK_STATUS.container]: false, + [TASK_STATUS.uploading]: false, + [TASK_STATUS.done]: false, + }; + + for (const [status, exp] of Object.entries(statusList)) { + const task = new Task({status}); + expect(task.isStopped()).toEqual(exp); + } + }); + + test('should use status for isInterrupted', () => { + const statusList = { + [TASK_STATUS.running]: false, + [TASK_STATUS.stoprequested]: false, + [TASK_STATUS.deleterequested]: false, + [TASK_STATUS.ultimatedeleterequested]: false, + [TASK_STATUS.resumerequested]: false, + [TASK_STATUS.requested]: false, + [TASK_STATUS.stopped]: false, + [TASK_STATUS.new]: false, + [TASK_STATUS.interrupted]: true, + [TASK_STATUS.container]: false, + [TASK_STATUS.uploading]: false, + [TASK_STATUS.done]: false, + }; + + for (const [status, exp] of Object.entries(statusList)) { + const task = new Task({status}); + expect(task.isInterrupted()).toEqual(exp); + } + }); + + test('should use status for isNew', () => { + const statusList = { + [TASK_STATUS.running]: false, + [TASK_STATUS.stoprequested]: false, + [TASK_STATUS.deleterequested]: false, + [TASK_STATUS.ultimatedeleterequested]: false, + [TASK_STATUS.resumerequested]: false, + [TASK_STATUS.requested]: false, + [TASK_STATUS.stopped]: false, + [TASK_STATUS.new]: true, + [TASK_STATUS.interrupted]: false, + [TASK_STATUS.container]: false, + [TASK_STATUS.uploading]: false, + [TASK_STATUS.done]: false, + }; + + for (const [status, exp] of Object.entries(statusList)) { + const task = new Task({status}); + expect(task.isNew()).toEqual(exp); + } + }); + + test('should be changeable if alterable or new', () => { + let task = new Task({status: TASK_STATUS.new, alterable: '0'}); + expect(task.isChangeable()).toEqual(true); + + task = new Task({status: TASK_STATUS.done, alterable: '1'}); + expect(task.isChangeable()).toEqual(true); + }); +}); From c727d71ecc18c8feb5c6526c4bb9428cd9c13a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 18 Mar 2019 14:06:43 +0100 Subject: [PATCH 10/55] Set default host_ordering when saving a task Update TaskCommand to use a default for host_ordering when saving a task. --- gsa/src/gmp/commands/tasks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsa/src/gmp/commands/tasks.js b/gsa/src/gmp/commands/tasks.js index 9d7e3c1819..f9199d8a2e 100644 --- a/gsa/src/gmp/commands/tasks.js +++ b/gsa/src/gmp/commands/tasks.js @@ -28,7 +28,7 @@ import ScanConfig from '../models/scanconfig'; import Scanner from '../models/scanner'; import Schedule from '../models/schedule'; import Target from '../models/target'; -import Task from '../models/task'; +import Task, {HOST_ORDERING_SEQUENTIAL} from '../models/task'; import EntitiesCommand from './entities'; import EntityCommand from './entity'; @@ -261,7 +261,7 @@ class TaskCommand extends EntityCommand { slave_id = 0, alert_ids = [], source_iface = '', - hosts_ordering, + hosts_ordering = HOST_ORDERING_SEQUENTIAL, max_checks, max_hosts, } = args; From 6e35b82847ac283bdfa28849411ce947fa4b8a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 18 Mar 2019 15:14:36 +0100 Subject: [PATCH 11/55] Use different variable for Task copy Allow access to original data when parsing a task. --- gsa/src/gmp/models/task.js | 70 +++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/gsa/src/gmp/models/task.js b/gsa/src/gmp/models/task.js index ba4ac24666..c212da9471 100644 --- a/gsa/src/gmp/models/task.js +++ b/gsa/src/gmp/models/task.js @@ -42,9 +42,9 @@ export const AUTO_DELETE_KEEP = 'keep'; export const AUTO_DELETE_NO = 'no'; export const AUTO_DELETE_DEFAULT_VALUE = 5; -export const HOST_ORDERING_SEQUENTIAL = 'sequential'; -export const HOST_ORDERING_RANDOM = 'random'; -export const HOST_ORDERING_REVERSE = 'reverse'; +export const HOSTS_ORDERING_SEQUENTIAL = 'sequential'; +export const HOSTS_ORDERING_RANDOM = 'random'; +export const HOSTS_ORDERING_REVERSE = 'reverse'; export const TASK_STATUS = { running: 'Running', @@ -133,18 +133,18 @@ class Task extends Model { } parseProperties(elem) { - elem = super.parseProperties(elem); + const copy = super.parseProperties(elem); const {report_count} = elem; if (isDefined(report_count)) { - elem.report_count = {...report_count}; - elem.report_count.total = parseInt(report_count.__text); - elem.report_count.finished = parseInt(report_count.finished); + copy.report_count = {...report_count}; + copy.report_count.total = parseInt(report_count.__text); + copy.report_count.finished = parseInt(report_count.finished); } - elem.alterable = parseYesNo(elem.alterable); - elem.result_count = parseInt(elem.result_count); + copy.alterable = parseYesNo(elem.alterable); + copy.result_count = parseInt(elem.result_count); const reports = [ 'first_report', @@ -156,7 +156,7 @@ class Task extends Model { reports.forEach(name => { const report = elem[name]; if (isDefined(report)) { - elem[name] = new Report(report.report); + copy[name] = new Report(report.report); } }); @@ -166,65 +166,65 @@ class Task extends Model { const data = elem[name]; if (isDefined(data) && !isEmpty(data._id)) { - elem[name] = new Model(data, normalizeType(name)); + copy[name] = new Model(data, normalizeType(name)); } else { - delete elem[name]; + delete copy[name]; } }); if (isDefined(elem.alert)) { - elem.alerts = map(elem.alert, alert => new Model(alert, 'alert')); - delete elem.alert; + copy.alerts = map(elem.alert, alert => new Model(alert, 'alert')); + delete copy.alert; } if (isDefined(elem.scanner) && !isEmpty(elem.scanner._id)) { - elem.scanner = new Scanner(elem.scanner); + copy.scanner = new Scanner(elem.scanner); } else { - delete elem.scanner; + delete copy.scanner; } if (isDefined(elem.schedule) && !isEmpty(elem.schedule._id)) { - elem.schedule = new Schedule(elem.schedule); + copy.schedule = new Schedule(elem.schedule); } else { - delete elem.schedule; + delete copy.schedule; } - elem.schedule_periods = parseInt(elem.schedule_periods); + copy.schedule_periods = parseInt(elem.schedule_periods); - elem.progress = parseProgressElement(elem.progress); + copy.progress = parseProgressElement(elem.progress); const prefs = {}; - if (elem.preferences && isArray(elem.preferences.preference)) { + if (copy.preferences && isArray(elem.preferences.preference)) { for (const pref of elem.preferences.preference) { switch (pref.scanner_name) { case 'in_assets': - elem.in_assets = parse_yes(pref.value); + copy.in_assets = parse_yes(pref.value); break; case 'assets_apply_overrides': - elem.apply_overrides = parse_yes(pref.value); + copy.apply_overrides = parse_yes(pref.value); break; case 'assets_min_qod': - elem.min_qod = parseInt(pref.value); + copy.min_qod = parseInt(pref.value); break; case 'auto_delete': - elem.auto_delete = + copy.auto_delete = pref.value === AUTO_DELETE_KEEP ? AUTO_DELETE_KEEP : AUTO_DELETE_NO; break; case 'auto_delete_data': - elem.auto_delete_data = + copy.auto_delete_data = pref.value === '0' ? AUTO_DELETE_DEFAULT_VALUE : parseInt(pref.value); break; case 'max_hosts': case 'max_checks': - elem[pref.scanner_name] = parseInt(pref.value); + copy[pref.scanner_name] = parseInt(pref.value); break; case 'source_iface': - elem.source_iface = pref.value; + copy.source_iface = pref.value; break; default: prefs[pref.scanner_name] = {value: pref.value, name: pref.name}; @@ -233,21 +233,21 @@ class Task extends Model { } } - elem.preferences = prefs; + copy.preferences = prefs; if (isDefined(elem.average_duration)) { - elem.average_duration = parseDuration(elem.average_duration); + copy.average_duration = parseDuration(elem.average_duration); } if ( - elem.host_ordering !== HOST_ORDERING_RANDOM && - elem.host_ordering !== HOST_ORDERING_REVERSE && - elem.host_ordering !== HOST_ORDERING_SEQUENTIAL + copy.host_ordering !== HOSTS_ORDERING_RANDOM && + copy.host_ordering !== HOSTS_ORDERING_REVERSE && + copy.host_ordering !== HOSTS_ORDERING_SEQUENTIAL ) { - delete elem.host_ordering; + delete copy.host_ordering; } - return elem; + return copy; } } From 3abd41d95b60d2e8d4e6da9a77b38f077365298c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 18 Mar 2019 15:20:37 +0100 Subject: [PATCH 12/55] Fix typo host -> hosts It's hosts_ordering and not host_ordering. Update constants accordingly. --- gsa/src/gmp/commands/tasks.js | 4 ++-- gsa/src/gmp/models/__tests__/task.js | 32 ++++++++++++++-------------- gsa/src/gmp/models/task.js | 8 +++---- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/gsa/src/gmp/commands/tasks.js b/gsa/src/gmp/commands/tasks.js index f9199d8a2e..ba179132c8 100644 --- a/gsa/src/gmp/commands/tasks.js +++ b/gsa/src/gmp/commands/tasks.js @@ -28,7 +28,7 @@ import ScanConfig from '../models/scanconfig'; import Scanner from '../models/scanner'; import Schedule from '../models/schedule'; import Target from '../models/target'; -import Task, {HOST_ORDERING_SEQUENTIAL} from '../models/task'; +import Task, {HOSTS_ORDERING_SEQUENTIAL} from '../models/task'; import EntitiesCommand from './entities'; import EntityCommand from './entity'; @@ -261,7 +261,7 @@ class TaskCommand extends EntityCommand { slave_id = 0, alert_ids = [], source_iface = '', - hosts_ordering = HOST_ORDERING_SEQUENTIAL, + hosts_ordering = HOSTS_ORDERING_SEQUENTIAL, max_checks, max_hosts, } = args; diff --git a/gsa/src/gmp/models/__tests__/task.js b/gsa/src/gmp/models/__tests__/task.js index 752f928d6c..52e4b31555 100644 --- a/gsa/src/gmp/models/__tests__/task.js +++ b/gsa/src/gmp/models/__tests__/task.js @@ -20,9 +20,9 @@ import 'core-js/fn/object/entries'; import Task, { - HOST_ORDERING_RANDOM, - HOST_ORDERING_REVERSE, - HOST_ORDERING_SEQUENTIAL, + HOSTS_ORDERING_RANDOM, + HOSTS_ORDERING_REVERSE, + HOSTS_ORDERING_SEQUENTIAL, TASK_STATUS, } from 'gmp/models/task'; import {testModelProperties} from '../testing'; @@ -30,30 +30,30 @@ import {testModelProperties} from '../testing'; testModelProperties(Task, 'task'); describe('Task model tests', () => { - test('should parse undefined host_ordering', () => { - const obj = {host_ordering: undefined}; + test('should parse undefined hosts_ordering', () => { + const obj = {hosts_ordering: undefined}; const task = new Task(obj); - expect(task.host_ordering).toBeUndefined(); + expect(task.hosts_ordering).toBeUndefined(); }); - test('should parse unkonw host_ordering as undefined', () => { - const obj = {host_ordering: 'foo'}; + test('should parse unknown hosts_ordering as undefined', () => { + const obj = {hosts_ordering: 'foo'}; const task = new Task(obj); - expect(task.host_ordering).toBeUndefined(); + expect(task.hosts_ordering).toBeUndefined(); }); - test('should parse known host_ordering', () => { - let obj = {host_ordering: HOST_ORDERING_RANDOM}; + test('should parse known hosts_ordering', () => { + let obj = {hosts_ordering: HOSTS_ORDERING_RANDOM}; let task = new Task(obj); - expect(task.host_ordering).toEqual(HOST_ORDERING_RANDOM); + expect(task.hosts_ordering).toEqual(HOSTS_ORDERING_RANDOM); - obj = {host_ordering: HOST_ORDERING_REVERSE}; + obj = {hosts_ordering: HOSTS_ORDERING_REVERSE}; task = new Task(obj); - expect(task.host_ordering).toEqual(HOST_ORDERING_REVERSE); + expect(task.hosts_ordering).toEqual(HOSTS_ORDERING_REVERSE); - obj = {host_ordering: HOST_ORDERING_SEQUENTIAL}; + obj = {hosts_ordering: HOSTS_ORDERING_SEQUENTIAL}; task = new Task(obj); - expect(task.host_ordering).toEqual(HOST_ORDERING_SEQUENTIAL); + expect(task.hosts_ordering).toEqual(HOSTS_ORDERING_SEQUENTIAL); }); }); diff --git a/gsa/src/gmp/models/task.js b/gsa/src/gmp/models/task.js index c212da9471..1b7aa519b5 100644 --- a/gsa/src/gmp/models/task.js +++ b/gsa/src/gmp/models/task.js @@ -240,11 +240,11 @@ class Task extends Model { } if ( - copy.host_ordering !== HOSTS_ORDERING_RANDOM && - copy.host_ordering !== HOSTS_ORDERING_REVERSE && - copy.host_ordering !== HOSTS_ORDERING_SEQUENTIAL + copy.hosts_ordering !== HOSTS_ORDERING_RANDOM && + copy.hosts_ordering !== HOSTS_ORDERING_REVERSE && + copy.hosts_ordering !== HOSTS_ORDERING_SEQUENTIAL ) { - delete copy.host_ordering; + delete copy.hosts_ordering; } return copy; From c5d0b5ff6dfdd018aac2b9d3d9b6ffc3f4e7d1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 18 Mar 2019 15:22:21 +0100 Subject: [PATCH 13/55] Use constant from Task model for default host ordering Import the default hosts ordering from task model module. --- gsa/src/web/pages/tasks/dialog.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gsa/src/web/pages/tasks/dialog.js b/gsa/src/web/pages/tasks/dialog.js index f2f76e6914..3630557572 100644 --- a/gsa/src/web/pages/tasks/dialog.js +++ b/gsa/src/web/pages/tasks/dialog.js @@ -28,7 +28,11 @@ import {selectSaveId} from 'gmp/utils/id'; import {NO_VALUE, YES_VALUE} from 'gmp/parser'; -import {AUTO_DELETE_KEEP, AUTO_DELETE_DEFAULT_VALUE} from 'gmp/models/task'; +import { + AUTO_DELETE_KEEP, + AUTO_DELETE_DEFAULT_VALUE, + HOSTS_ORDERING_SEQUENTIAL, +} from 'gmp/models/task'; import { OPENVAS_SCANNER_TYPE, @@ -142,7 +146,6 @@ ScannerSelect.propTypes = { const DEFAULT_MAX_CHECKS = 4; const DEFAULT_MAX_HOSTS = 20; const DEFAULT_MIN_QOD = 70; -const DEFAULT_HOSTS_ORDERING = 'sequential'; const TaskDialog = ({ add_tag = NO_VALUE, @@ -155,7 +158,7 @@ const TaskDialog = ({ capabilities, comment = '', config_id = FULL_AND_FAST_SCAN_CONFIG_ID, - hosts_ordering = DEFAULT_HOSTS_ORDERING, + hosts_ordering = HOSTS_ORDERING_SEQUENTIAL, in_assets = YES_VALUE, max_checks = DEFAULT_MAX_CHECKS, max_hosts = DEFAULT_MAX_HOSTS, From 8befeaf5649a5b98a5e998bfec05f356c6417f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 19 Mar 2019 10:58:56 +0100 Subject: [PATCH 14/55] Remove obsolete box prop from Layout The box prop has been removed a long time ago from the Layout component. This removes a unknown prop warning raised by react. --- gsa/src/web/pages/tasks/dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsa/src/web/pages/tasks/dialog.js b/gsa/src/web/pages/tasks/dialog.js index 3630557572..e41d8e7e92 100644 --- a/gsa/src/web/pages/tasks/dialog.js +++ b/gsa/src/web/pages/tasks/dialog.js @@ -384,7 +384,7 @@ const TaskDialog = ({ value={state.min_qod} onChange={onValueChange} /> - % + % {change_task && ( From a10497978d615c0484ffb39875e022008a5ca4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 19 Mar 2019 11:00:20 +0100 Subject: [PATCH 15/55] Update label for adding a tag to a new task --- gsa/src/web/pages/tasks/dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsa/src/web/pages/tasks/dialog.js b/gsa/src/web/pages/tasks/dialog.js index e41d8e7e92..8c36d47242 100644 --- a/gsa/src/web/pages/tasks/dialog.js +++ b/gsa/src/web/pages/tasks/dialog.js @@ -499,7 +499,7 @@ const TaskDialog = ({ Date: Tue, 19 Mar 2019 11:03:57 +0100 Subject: [PATCH 16/55] Disable tag selection if add tag checkbox is not selected Don't allow to select a tag if the add tag checkbox is not checked. --- gsa/src/web/pages/tasks/dialog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gsa/src/web/pages/tasks/dialog.js b/gsa/src/web/pages/tasks/dialog.js index 8c36d47242..00a54efc9f 100644 --- a/gsa/src/web/pages/tasks/dialog.js +++ b/gsa/src/web/pages/tasks/dialog.js @@ -507,6 +507,7 @@ const TaskDialog = ({ onChange={onValueChange} /> + + Do not automatically delete reports + + + + +
+
+ + + + + ▲ + + + ▼ + + + + reports + +
+
+ + +`; diff --git a/gsa/src/web/pages/tasks/__tests__/autodeletereportsgroup.js b/gsa/src/web/pages/tasks/__tests__/autodeletereportsgroup.js new file mode 100644 index 0000000000..7b0e12d1a8 --- /dev/null +++ b/gsa/src/web/pages/tasks/__tests__/autodeletereportsgroup.js @@ -0,0 +1,118 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import React from 'react'; + +import {render, fireEvent} from 'web/utils/testing'; + +import AutoDeleteReportsGroup from '../autodeletereportsgroup'; +import { + AUTO_DELETE_KEEP, + AUTO_DELETE_KEEP_DEFAULT_VALUE, + AUTO_DELETE_NO, +} from 'gmp/models/task'; + +describe('AutoDeleteReportsGroup tests', () => { + test('should render dialog group', () => { + const handleChange = jest.fn(); + + const {element} = render( + , + ); + + expect(element).toMatchSnapshot(); + }); + + test('should allow to change auto delete no', () => { + const handleChange = jest.fn(); + + const {queryAllByTestId} = render( + , + ); + + const [autoDeleteNoRadio] = queryAllByTestId('radio-input'); + + fireEvent.click(autoDeleteNoRadio); + + expect(handleChange).toHaveBeenCalledWith(AUTO_DELETE_NO, 'auto_delete'); + }); + + test('should allow to change auto delete keep', () => { + const handleChange = jest.fn(); + + const {queryAllByTestId} = render( + , + ); + + const [, autoDeleteKeepRadio] = queryAllByTestId('radio-input'); + + fireEvent.click(autoDeleteKeepRadio); + + expect(handleChange).toHaveBeenCalledWith(AUTO_DELETE_KEEP, 'auto_delete'); + }); + + test('should allow to change auto delete keep value', () => { + const handleChange = jest.fn(); + + const {getByTestId} = render( + , + ); + + const autoDeleteKeepData = getByTestId('spinner-input'); + + fireEvent.change(autoDeleteKeepData, {target: {value: 10}}); + + expect(handleChange).toHaveBeenCalledWith(10, 'auto_delete_data'); + }); + + test('should not allow to change auto delete keep value', () => { + const handleChange = jest.fn(); + + const {getByTestId} = render( + , + ); + + const autoDeleteKeepData = getByTestId('spinner-input'); + + fireEvent.change(autoDeleteKeepData, {target: {value: 10}}); + + expect(handleChange).not.toHaveBeenCalled(); + + expect(autoDeleteKeepData).toBeDisabled(); + }); +}); From 505efc0bd7e726d64d986e86572bf18c1e173e73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 20 Mar 2019 15:12:50 +0100 Subject: [PATCH 50/55] Add helper testing function to get elments by name attribute This allows to get form fields more easily. Don't know why it isn't provided by dom-testing-library already. --- gsa/src/web/utils/testing.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/gsa/src/web/utils/testing.js b/gsa/src/web/utils/testing.js index e708dde71a..a9707bb542 100644 --- a/gsa/src/web/utils/testing.js +++ b/gsa/src/web/utils/testing.js @@ -23,7 +23,12 @@ import 'jest-dom/extend-expect'; import React from 'react'; -import {render as reactTestingRender, cleanup} from 'react-testing-library'; +import { + render as reactTestingRender, + cleanup, + queryAllByAttribute, + getElementError, +} from 'react-testing-library'; import {Router} from 'react-router-dom'; @@ -44,11 +49,28 @@ export * from 'react-testing-library'; afterEach(cleanup); +const queryAllByName = (container, name) => + queryAllByAttribute('name', container, name); + +const getByName = (container, name) => { + const els = queryAllByName(container, name); + if (!els.length) { + throw getElementError( + `Unable to find an element with the name: ${name}.`, + container, + ); + } + return els[0]; +}; + export const render = ui => { - const {container, ...other} = reactTestingRender(ui); + const {container, baseElement, ...other} = reactTestingRender(ui); return { + baseElement, container, element: hasValue(container) ? container.firstChild : undefined, + getByName: name => getByName(baseElement, name), + queryAllByName: name => queryAllByName(baseElement, name), ...other, }; }; From 4f4711e13193741c653d35c3dc8d3403685cbb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 20 Mar 2019 15:14:58 +0100 Subject: [PATCH 51/55] Use AUTO_DELETE_KEEP_DEFAULT_VALUE at ContainerDialog --- gsa/src/web/pages/tasks/containerdialog.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gsa/src/web/pages/tasks/containerdialog.js b/gsa/src/web/pages/tasks/containerdialog.js index 5c4d959a99..0cc0c9681b 100644 --- a/gsa/src/web/pages/tasks/containerdialog.js +++ b/gsa/src/web/pages/tasks/containerdialog.js @@ -24,7 +24,10 @@ import {isDefined} from 'gmp/utils/identity'; import {YES_VALUE} from 'gmp/parser'; -import {AUTO_DELETE_KEEP} from 'gmp/models/task'; +import { + AUTO_DELETE_KEEP, + AUTO_DELETE_KEEP_DEFAULT_VALUE, +} from 'gmp/models/task'; import PropTypes from '../../utils/proptypes.js'; @@ -40,7 +43,7 @@ import AutoDeleteReportsGroup from './autodeletereportsgroup.js'; const ContainerTaskDialog = ({ auto_delete = AUTO_DELETE_KEEP, - auto_delete_data = 5, + auto_delete_data = AUTO_DELETE_KEEP_DEFAULT_VALUE, comment, in_assets = YES_VALUE, name, From bb4ef173b41b09a2cc8c39cc5deec09b80a905b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 20 Mar 2019 15:15:27 +0100 Subject: [PATCH 52/55] Add tests for ContainerTaskDialog --- .../__snapshots__/containerdialog.js.snap | 1410 +++++++++++++++++ .../pages/tasks/__tests__/containerdialog.js | 149 ++ 2 files changed, 1559 insertions(+) create mode 100644 gsa/src/web/pages/tasks/__tests__/__snapshots__/containerdialog.js.snap create mode 100644 gsa/src/web/pages/tasks/__tests__/containerdialog.js diff --git a/gsa/src/web/pages/tasks/__tests__/__snapshots__/containerdialog.js.snap b/gsa/src/web/pages/tasks/__tests__/__snapshots__/containerdialog.js.snap new file mode 100644 index 0000000000..df0c62fb0a --- /dev/null +++ b/gsa/src/web/pages/tasks/__tests__/__snapshots__/containerdialog.js.snap @@ -0,0 +1,1410 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ContainerDialog tests should render create dialog 1`] = ` +.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c8 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + +.c10 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; +} + +.c14 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c19 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c1 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + margin: 10% auto; + border: 0; + outline: 0; + width: 800px; + height: px; +} + +.c0 { + position: fixed; + font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif; + font-size: 1.1em; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; + background: rgba(102,102,102,0.5); + z-index: 100; + -webkit-transition: opacity 1s ease-in; + transition: opacity 1s ease-in; + width: 100%; + height: 100%; +} + +.c24 { + width: 0; + height: 0; + cursor: nwse-resize; + border-right: 20px solid transparent; + border-bottom: 20px solid transparent; + border-top: 20px solid #fff; +} + +.c23 { + position: absolute; + bottom: 3px; + right: 3px; + width: 20px; + height: 20px; + background: repeating-linear-gradient( -45deg, transparent, transparent 2px, #000 2px, #000 3px ); +} + +.c2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: inherit; + padding: 0; + background: #fff; + box-shadow: 5px 5px 10px #787878; + border-radius: 3px; + border: 1px solid #787878; +} + +.c6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + border: 1px solid #519032; + font-weight: bold; + font-size: 12px; + font-family: Verdana,sans-serif; + color: #519032; + cursor: pointer; + border-radius: 2px; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; +} + +.c6:hover { + color: #fff; + background: #519032; +} + +.c5 { + height: 24px; + width: 24px; + line-height: 24px; +} + +.c5 * { + height: inherit; + width: inherit; +} + +.c22 { + display: inline-block; + padding: 0 15px; + color: #393637; + text-align: center; + vertical-align: middle; + font-size: 11px; + font-weight: bold; + line-height: 30px; + -webkit-text-decoration: none; + text-decoration: none; + white-space: nowrap; + background-color: #fff; + border-radius: 2px; + border: 1px solid #aaaaaa; + cursor: pointer; + overflow: visible; + z-index: 1; +} + +.c22:focus, +.c22:hover { + border: 1px solid #393637; +} + +.c22:hover { + -webkit-text-decoration: none; + text-decoration: none; + background: #66c430; + font-weight: bold; + color: #fff; +} + +.c22[disabled] { + cursor: not-allowed; + opacity: 0.65; + box-shadow: none; +} + +.c22 img { + height: 32px; + width: 32px; + margin-top: 5px 10px 5px -10px; + vertical-align: middle; +} + +.c22:link { + -webkit-text-decoration: none; + text-decoration: none; + color: #393637; +} + +.c21 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c20 { + border: 1px solid #787878; + color: #519032; + background: #87d050; +} + +.c20:hover { + color: #fff; + background: #519032; +} + +.c18 { + border-width: 1px 0 0 0; + border-style: solid; + border-color: #c8d3d9; + margin-top: 15px; + padding: 10px 20px 10px 20px; +} + +.c17 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + padding: 5px 5px 5px 10px; + margin-bottom: 15px; + border-radius: 2px 2px 0 0; + border-bottom: 1px solid #787878; + color: #fff; + font-weight: bold; + background: #66c430; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.c9 { + overflow: auto; + padding: 0 15px; + width: 100%; + height: 100%; + max-height: 400px; +} + +.c7 { + overflow: hidden; + height: 100%; +} + +.c11 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + padding-bottom: 10px; +} + +.c12 { + display: inline-block; + max-width: 100%; + font-weight: bold; + text-align: right; + padding-left: 10px; + padding-right: 10px; + width: 16.66666667%; + margin-left: 0; +} + +.c13 { + width: 83.33333333%; + padding-left: 10px; + padding-right: 10px; +} + +.c16 { + font-family: inherit; + font-size: inherit; + line-height: inherit; + display: block; + height: 22px; + color: #393637; + background-color: #fff; + background-image: none; + border: 1px solid #aaaaaa; + border-radius: 2px; + padding: 1px 8px; +} + +.c16:-webkit-autofill { + box-shadow: 0 0 0 1000px white inset; +} + +.c15 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + + +
+
+
+ +
+
+
+ +`; + +exports[`ContainerDialog tests should render edit dialog 1`] = ` + +
+ .c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c8 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; +} + +.c10 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; +} + +.c14 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c30 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-box-pack: end; + -webkit-justify-content: flex-end; + -ms-flex-pack: end; + justify-content: flex-end; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c18 { + margin-left: -5px; +} + +.c18 > * { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.c18 > * { + margin-left: 5px; +} + +.c17 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; +} + +.c1 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + margin: 10% auto; + border: 0; + outline: 0; + width: 800px; + height: px; +} + +.c0 { + position: fixed; + font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif; + font-size: 1.1em; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; + background: rgba(102,102,102,0.5); + z-index: 100; + -webkit-transition: opacity 1s ease-in; + transition: opacity 1s ease-in; + width: 100%; + height: 100%; +} + +.c35 { + width: 0; + height: 0; + cursor: nwse-resize; + border-right: 20px solid transparent; + border-bottom: 20px solid transparent; + border-top: 20px solid #fff; +} + +.c34 { + position: absolute; + bottom: 3px; + right: 3px; + width: 20px; + height: 20px; + background: repeating-linear-gradient( -45deg, transparent, transparent 2px, #000 2px, #000 3px ); +} + +.c2 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: inherit; + padding: 0; + background: #fff; + box-shadow: 5px 5px 10px #787878; + border-radius: 3px; + border: 1px solid #787878; +} + +.c6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + border: 1px solid #519032; + font-weight: bold; + font-size: 12px; + font-family: Verdana,sans-serif; + color: #519032; + cursor: pointer; + border-radius: 2px; + padding: 0; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; +} + +.c6:hover { + color: #fff; + background: #519032; +} + +.c5 { + height: 24px; + width: 24px; + line-height: 24px; +} + +.c5 * { + height: inherit; + width: inherit; +} + +.c33 { + display: inline-block; + padding: 0 15px; + color: #393637; + text-align: center; + vertical-align: middle; + font-size: 11px; + font-weight: bold; + line-height: 30px; + -webkit-text-decoration: none; + text-decoration: none; + white-space: nowrap; + background-color: #fff; + border-radius: 2px; + border: 1px solid #aaaaaa; + cursor: pointer; + overflow: visible; + z-index: 1; +} + +.c33:focus, +.c33:hover { + border: 1px solid #393637; +} + +.c33:hover { + -webkit-text-decoration: none; + text-decoration: none; + background: #66c430; + font-weight: bold; + color: #fff; +} + +.c33[disabled] { + cursor: not-allowed; + opacity: 0.65; + box-shadow: none; +} + +.c33 img { + height: 32px; + width: 32px; + margin-top: 5px 10px 5px -10px; + vertical-align: middle; +} + +.c33:link { + -webkit-text-decoration: none; + text-decoration: none; + color: #393637; +} + +.c32 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c31 { + border: 1px solid #787878; + color: #519032; + background: #87d050; +} + +.c31:hover { + color: #fff; + background: #519032; +} + +.c29 { + border-width: 1px 0 0 0; + border-style: solid; + border-color: #c8d3d9; + margin-top: 15px; + padding: 10px 20px 10px 20px; +} + +.c28 { + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.c3 { + padding: 5px 5px 5px 10px; + margin-bottom: 15px; + border-radius: 2px 2px 0 0; + border-bottom: 1px solid #787878; + color: #fff; + font-weight: bold; + background: #66c430; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -webkit-justify-content: space-between; + -ms-flex-pack: justify; + justify-content: space-between; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.c9 { + overflow: auto; + padding: 0 15px; + width: 100%; + height: 100%; + max-height: 400px; +} + +.c7 { + overflow: hidden; + height: 100%; +} + +.c11 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + padding-bottom: 10px; +} + +.c12 { + display: inline-block; + max-width: 100%; + font-weight: bold; + text-align: right; + padding-left: 10px; + padding-right: 10px; + width: 16.66666667%; + margin-left: 0; +} + +.c13 { + width: 83.33333333%; + padding-left: 10px; + padding-right: 10px; +} + +.c16 { + font-family: inherit; + font-size: inherit; + line-height: inherit; + display: block; + height: 22px; + color: #393637; + background-color: #fff; + background-image: none; + border: 1px solid #aaaaaa; + border-radius: 2px; + padding: 1px 8px; +} + +.c16:-webkit-autofill { + box-shadow: 0 0 0 1000px white inset; +} + +.c15 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-pack: start; + -webkit-justify-content: start; + -ms-flex-pack: start; + justify-content: start; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c19 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + font-weight: normal; + cursor: pointer; +} + +.c21 { + font-family: inherit; + font-size: inherit; + padding: 0; + margin: 0; + margin-left: 10px; + line-height: normal; + width: auto; + height: auto; +} + +.c22 { + opacity: 1; +} + +.c20 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.c23 { + border-radius: 2px; + border: 1px solid #c8d3d9; + background-color: #fff; + font-size: 1.1em; + position: relative; + display: inline-block; + overflow: hidden; + padding: 0; + vertical-align: middle; +} + +.c24 { + font-family: inherit; + font-size: inherit; + line-height: inherit; + border: none; + background: none; + color: inherit; + padding: 0; + margin: 0.2em 0; + vertical-align: middle; + margin-left: 0.4em; + margin-right: 22px; +} + +.c26 { + background-color: #c8d3d9; + color: #787878; + border-left: 1px solid #393637; + width: 16px; + height: 50%; + font-size: 0.6em; + padding: 0; + margin: 0; + text-align: center; + vertical-align: middle; + position: absolute; + right: 0; + cursor: default; + display: block; + overflow: hidden; + -webkit-text-decoration: none; + text-decoration: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c26:hover { + background-color: #66c430; + color: #fff; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c26:active { + background-color: #fff; + color: #519032; + -webkit-text-decoration: none; + text-decoration: none; +} + +.c25 { + border-top-right-radius: 1px; + top: 0; +} + +.c27 { + border-bottom-right-radius: 1px; + bottom: 0; +} + +
+
+ +
+
+
+ +`; diff --git a/gsa/src/web/pages/tasks/__tests__/containerdialog.js b/gsa/src/web/pages/tasks/__tests__/containerdialog.js new file mode 100644 index 0000000000..b92e0d6e71 --- /dev/null +++ b/gsa/src/web/pages/tasks/__tests__/containerdialog.js @@ -0,0 +1,149 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import React from 'react'; + +import {render, fireEvent} from 'web/utils/testing'; + +import ContainerDialog from '../containerdialog'; +import Task, { + AUTO_DELETE_KEEP, + AUTO_DELETE_KEEP_DEFAULT_VALUE, + AUTO_DELETE_NO, +} from 'gmp/models/task'; + +describe('ContainerDialog tests', () => { + test('should render create dialog', () => { + const handleClose = jest.fn(); + const handleSave = jest.fn(); + + const {baseElement} = render( + , + ); + + expect(baseElement).toMatchSnapshot(); + }); + + test('should render edit dialog', () => { + const task = new Task({name: 'foo', _id: 't1'}); + const handleClose = jest.fn(); + const handleSave = jest.fn(); + + const {baseElement} = render( + , + ); + + expect(baseElement).toMatchSnapshot(); + }); + + test('should change fields in create dialog', () => { + const handleClose = jest.fn(); + const handleSave = jest.fn(); + + const {getByName, getByTestId} = render( + , + ); + + const nameInput = getByName('name'); + fireEvent.change(nameInput, {target: {value: 'ipsum'}}); + + const commentInput = getByName('comment'); + fireEvent.change(commentInput, {target: {value: 'lorem'}}); + + const saveButton = getByTestId('dialog-save-button'); + fireEvent.click(saveButton); + + expect(handleSave).toHaveBeenCalledWith({ + comment: 'lorem', + auto_delete: AUTO_DELETE_KEEP, + auto_delete_data: AUTO_DELETE_KEEP_DEFAULT_VALUE, + in_assets: 1, + id: undefined, + name: 'ipsum', + }); + }); + + test('should change fields in edit dialog', () => { + const task = new Task({name: 'foo', _id: 't1'}); + const handleClose = jest.fn(); + const handleSave = jest.fn(); + + const {getByName, queryAllByName, getByTestId} = render( + , + ); + + const nameInput = getByName('name'); + fireEvent.change(nameInput, {target: {value: 'ipsum'}}); + + const commentInput = getByName('comment'); + fireEvent.change(commentInput, {target: {value: 'lorem'}}); + + const autoDeleteKeepInput = getByName('auto_delete_data'); + fireEvent.change(autoDeleteKeepInput, {target: {value: '10'}}); + + const [autoDeleteNoRadio] = queryAllByName('auto_delete'); + fireEvent.click(autoDeleteNoRadio); + + const [, inAssetsNoRadio] = queryAllByName('in_assets'); + fireEvent.click(inAssetsNoRadio); + + const saveButton = getByTestId('dialog-save-button'); + fireEvent.click(saveButton); + + expect(handleSave).toHaveBeenCalledWith({ + comment: 'lorem', + auto_delete: AUTO_DELETE_NO, + auto_delete_data: 10, + in_assets: 0, + id: 't1', + name: 'ipsum', + }); + }); + + test('should allow to close the dialog', () => { + const handleClose = jest.fn(); + const handleSave = jest.fn(); + + const {getByTestId} = render( + , + ); + + const closeButton = getByTestId('dialog-close-button'); + + fireEvent.click(closeButton); + + expect(handleClose).toHaveBeenCalled(); + expect(handleSave).not.toHaveBeenCalled(); + }); +}); From d5b26de4499427270f5e5e8bc4854211e5dc83cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Wed, 20 Mar 2019 15:41:28 +0100 Subject: [PATCH 53/55] Update CHANGELOG for task dialog fixes --- CHANGES.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cb66d55b14..93db76c602 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,7 +27,15 @@ $ cd gsa && git log * Sort alerts at task details alphanumerically #1094 * Add Alemba vFire alert to GUI #1100 * Add Sourcefire PKCS12 password support #1150 - + * Fix crash of Task dialog without user having get_config, get_scanner, + get_tags and get_targets permissions #1220 + * Ensure host ordering is valid in task dialog #1220 + * Disable tag selection if not task should be added in create task dialog #1220 + * Don't allow to add when editing a task #1220 + * Fix race condition resulting in not displaying scan config details at task + dialog when opening the dialog for the first time #1220 + * Fix saving run schedule once setting from Task dialog #1220 + * Don't create a container task from the task dialog accidentially #1220 ## gsa 8.0+beta2 (2018-12-04) From 75cfc2fdf423fbcbfe7ee84546de1ccd31988498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Thu, 21 Mar 2019 09:09:15 +0100 Subject: [PATCH 54/55] Use "Do not delete reports" as default in TaskDialog In GSA 7 the default has been not to delete any report of a task. We should keep the same default. --- CHANGES.md | 2 ++ gsa/src/web/pages/tasks/autodeletereportsgroup.js | 2 +- gsa/src/web/pages/tasks/dialog.js | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 93db76c602..42fe38fef5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,8 @@ $ cd gsa && git log dialog when opening the dialog for the first time #1220 * Fix saving run schedule once setting from Task dialog #1220 * Don't create a container task from the task dialog accidentially #1220 + * Use "Do not automatically delete reports" as default again in task dialog + #1220 ## gsa 8.0+beta2 (2018-12-04) diff --git a/gsa/src/web/pages/tasks/autodeletereportsgroup.js b/gsa/src/web/pages/tasks/autodeletereportsgroup.js index 97adbd95b2..d67f8550fd 100644 --- a/gsa/src/web/pages/tasks/autodeletereportsgroup.js +++ b/gsa/src/web/pages/tasks/autodeletereportsgroup.js @@ -31,7 +31,7 @@ import Spinner from '../../components/form/spinner.js'; import Divider from '../../components/layout/divider.js'; const AutoDeleteReportsGroup = ({ - autoDelete = AUTO_DELETE_KEEP, + autoDelete = AUTO_DELETE_NO, autoDeleteData, onChange, }) => ( diff --git a/gsa/src/web/pages/tasks/dialog.js b/gsa/src/web/pages/tasks/dialog.js index 1810f31146..03026d385f 100644 --- a/gsa/src/web/pages/tasks/dialog.js +++ b/gsa/src/web/pages/tasks/dialog.js @@ -30,9 +30,9 @@ import {selectSaveId} from 'gmp/utils/id'; import {NO_VALUE, YES_VALUE} from 'gmp/parser'; import { - AUTO_DELETE_KEEP, AUTO_DELETE_KEEP_DEFAULT_VALUE, HOSTS_ORDERING_SEQUENTIAL, + AUTO_DELETE_NO, } from 'gmp/models/task'; import { @@ -182,7 +182,7 @@ const TaskDialog = ({ alerts = [], alterable = NO_VALUE, apply_overrides = YES_VALUE, - auto_delete = AUTO_DELETE_KEEP, + auto_delete = AUTO_DELETE_NO, auto_delete_data = AUTO_DELETE_KEEP_DEFAULT_VALUE, capabilities, comment = '', From 7dfd74d273d5423cb51c87286b5895b79d4fc456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 22 Mar 2019 08:10:06 +0100 Subject: [PATCH 55/55] Rephrase changelog entry about add tag in edit task dialog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 42fe38fef5..948ee19c29 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,7 +31,7 @@ $ cd gsa && git log get_tags and get_targets permissions #1220 * Ensure host ordering is valid in task dialog #1220 * Disable tag selection if not task should be added in create task dialog #1220 - * Don't allow to add when editing a task #1220 + * Don't show add tag fields when editing a task #1220 * Fix race condition resulting in not displaying scan config details at task dialog when opening the dialog for the first time #1220 * Fix saving run schedule once setting from Task dialog #1220