diff --git a/gsa/src/web/graphql/__mocks__/alerts.js b/gsa/src/web/graphql/__mocks__/alerts.js
new file mode 100644
index 0000000000..0001b3491d
--- /dev/null
+++ b/gsa/src/web/graphql/__mocks__/alerts.js
@@ -0,0 +1,122 @@
+/* Copyright (C) 2020 Greenbone Networks GmbH
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {deepFreeze, createGenericQueryMock} from 'web/utils/testing';
+
+import {GET_ALERTS} from '../alerts';
+
+const alert1 = deepFreeze({
+ id: '1',
+ name: 'alert 1',
+ inUse: true,
+ writable: true,
+ active: true,
+ creationTime: '2020-08-06T11:34:15+00:00',
+ modificationTime: '2020-08-06T11:34:15+00:00',
+ owner: 'admin',
+ method: {
+ type: 'Alemba vFire',
+ data: [
+ {name: 'report_formats', value: 'c1645568-627a-11e3-a660-406186ea4fc5'},
+ {name: 'vfire_base_url', value: '127.0.0.1'},
+ {name: 'vfire_call_type_name', value: 'foo'},
+ {name: 'delta_type', value: 'None'},
+ {name: 'vfire_client_id', value: 'clientID'},
+ {name: 'vfire_call_partition_name', value: 'lorem'},
+ {name: 'delta_report_id', value: null},
+ {name: 'vfire_call_template_name', value: 'bar'},
+ {name: 'vfire_call_urgency_name', value: 'hello'},
+ {name: 'vfire_call_impact_name', value: 'baz'},
+ {name: 'details_url', value: 'https://secinfo.greenbone.net/etc'},
+ ],
+ },
+ event: {
+ type: 'Task run status changed',
+ data: [{name: 'status', value: 'Done'}],
+ },
+ condition: {
+ type: 'Always',
+ data: [],
+ },
+ permissions: [{name: 'Everything'}],
+ tasks: [
+ {id: '8589296f-5051-4ed9-9d86-c022936e2893', name: 'task_with_alerts'},
+ {id: '173a38fe-1038-48a6-9c48-a623ffc04ba8', name: 'scan_local'},
+ ],
+});
+
+const alert2 = deepFreeze({
+ id: '2',
+ name: 'alert 2',
+ inUse: true,
+ writable: false,
+ active: true,
+ creationTime: '2020-08-06T11:30:41+00:00',
+ modificationTime: '2020-08-07T09:26:05+00:00',
+ owner: 'admin',
+ method: {
+ type: 'Email',
+ data: [
+ {name: 'notice', value: '1'},
+ {name: 'from_address', value: 'foo@bar.com'},
+ {name: 'delta_type', value: 'None'},
+ {name: 'to_address', value: 'foo@bar.com'},
+ {name: 'delta_report_id', value: null},
+ {name: 'subject', value: '[GVM] $T $q $S since $d'},
+ {name: 'details_url', value: 'https://secinfo.greenbone.net/etc'},
+ ],
+ },
+ event: {
+ type: 'Updated SecInfo arrived',
+ data: [{name: 'secinfo_type', value: 'nvt'}],
+ },
+ condition: {
+ type: 'Filter count at least',
+ data: [{name: 'count', value: '3'}],
+ },
+ permissions: [{name: 'Everything'}],
+ tasks: null,
+});
+
+const mockAlerts = {
+ edges: [
+ {
+ node: alert1,
+ },
+ {
+ node: alert2,
+ },
+ ],
+ counts: {
+ total: 2,
+ filtered: 2,
+ offset: 0,
+ limit: 10,
+ length: 2,
+ },
+ pageInfo: {
+ hasNextPage: false,
+ hasPreviousPage: false,
+ startCursor: 'YWxlcnQ6MA==',
+ endCursor: 'YWxlcnQ6OQ==',
+ lastPageCursor: 'YWxlcnQ6MA==',
+ },
+};
+
+export const createGetAlertsQueryMock = () =>
+ createGenericQueryMock(GET_ALERTS, {alerts: mockAlerts});
diff --git a/gsa/src/web/graphql/__tests__/alerts.js b/gsa/src/web/graphql/__tests__/alerts.js
new file mode 100644
index 0000000000..5f5a443d3e
--- /dev/null
+++ b/gsa/src/web/graphql/__tests__/alerts.js
@@ -0,0 +1,102 @@
+/* Copyright (C) 2020 Greenbone Networks GmbH
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import React from 'react';
+
+import {isDefined} from 'gmp/utils/identity';
+
+import {rendererWith, screen, wait, fireEvent} from 'web/utils/testing';
+
+import {useLazyGetAlerts} from '../alerts';
+import {createGetAlertsQueryMock} from '../__mocks__/alerts';
+
+const GetLazyAlertsComponent = () => {
+ const [getAlerts, {counts, loading, alerts}] = useLazyGetAlerts();
+
+ if (loading) {
+ return Loading;
+ }
+ return (
+
+
+ );
+};
+
+describe('useLazyGetAlert tests', () => {
+ test('should query alerts after user interaction', async () => {
+ const [mock, resultFunc] = createGetAlertsQueryMock();
+ const {render} = rendererWith({queryMocks: [mock]});
+
+ render();
+
+ let alertElements = screen.queryAllByTestId('alert');
+ expect(alertElements).toHaveLength(0);
+
+ let noAlerts = screen.queryByTestId('no-alert');
+ expect(noAlerts).toBeInTheDocument();
+ const noCounts = screen.queryByTestId('no-counts');
+ expect(noCounts).toBeInTheDocument();
+
+ const button = screen.getByTestId('load');
+ fireEvent.click(button);
+
+ expect(screen.getByTestId('loading')).toHaveTextContent('Loading');
+
+ await wait();
+
+ expect(resultFunc).toHaveBeenCalled();
+
+ alertElements = screen.getAllByTestId('alert');
+ expect(alertElements).toHaveLength(2);
+
+ expect(alertElements[0]).toHaveTextContent('alert 1');
+ expect(alertElements[1]).toHaveTextContent('alert 2');
+
+ noAlerts = screen.queryByTestId('no-alert');
+ expect(noAlerts).not.toBeInTheDocument();
+
+ expect(screen.getByTestId('total')).toHaveTextContent(2);
+ expect(screen.getByTestId('filtered')).toHaveTextContent(2);
+ expect(screen.getByTestId('first')).toHaveTextContent(1);
+ expect(screen.getByTestId('limit')).toHaveTextContent(10);
+ expect(screen.getByTestId('length')).toHaveTextContent(2);
+ });
+});
diff --git a/gsa/src/web/graphql/alerts.js b/gsa/src/web/graphql/alerts.js
new file mode 100644
index 0000000000..3636a732a4
--- /dev/null
+++ b/gsa/src/web/graphql/alerts.js
@@ -0,0 +1,131 @@
+/* Copyright (C) 2020 Greenbone Networks GmbH
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3
+ * 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import {useCallback} from 'react';
+
+import gql from 'graphql-tag';
+
+import {useLazyQuery} from '@apollo/react-hooks';
+
+import CollectionCounts from 'gmp/collection/collectioncounts';
+
+import Alert from 'gmp/models/alert';
+
+import {isDefined} from 'gmp/utils/identity';
+
+export const GET_ALERTS = gql`
+ query Alert(
+ $filterString: FilterString
+ $after: String
+ $before: String
+ $first: Int
+ $last: Int
+ ) {
+ alerts(
+ filterString: $filterString
+ after: $after
+ before: $before
+ first: $first
+ last: $last
+ ) {
+ edges {
+ node {
+ name
+ id
+ inUse
+ writable
+ owner
+ creationTime
+ modificationTime
+ tasks {
+ id
+ name
+ }
+ event {
+ type
+ data {
+ name
+ value
+ }
+ }
+ condition {
+ type
+ data {
+ name
+ value
+ }
+ }
+ method {
+ type
+ data {
+ name
+ value
+ }
+ }
+ permissions {
+ name
+ }
+ active
+ }
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ endCursor
+ lastPageCursor
+ }
+ counts {
+ total
+ filtered
+ offset
+ limit
+ length
+ }
+ }
+ }
+`;
+
+export const useLazyGetAlerts = (variables, options) => {
+ const [queryAlerts, {data, ...other}] = useLazyQuery(GET_ALERTS, {
+ ...options,
+ variables,
+ });
+ const alerts = isDefined(data?.alerts)
+ ? data.alerts.edges.map(entity => Alert.fromObject(entity.node))
+ : undefined;
+
+ const {total, filtered, offset = -1, limit, length} =
+ data?.alerts?.counts || {};
+ const counts = isDefined(data?.alerts?.counts)
+ ? new CollectionCounts({
+ all: total,
+ filtered: filtered,
+ first: offset + 1,
+ length: length,
+ rows: limit,
+ })
+ : undefined;
+ const getAlerts = useCallback(
+ // eslint-disable-next-line no-shadow
+ (variables, options) => queryAlerts({...options, variables}),
+ [queryAlerts],
+ );
+ const pageInfo = data?.alerts?.pageInfo;
+ return [getAlerts, {...other, counts, alerts, pageInfo}];
+};
diff --git a/gsa/src/web/pages/tasks/component.js b/gsa/src/web/pages/tasks/component.js
index 950768969e..612f271547 100644
--- a/gsa/src/web/pages/tasks/component.js
+++ b/gsa/src/web/pages/tasks/component.js
@@ -37,11 +37,6 @@ import date from 'gmp/models/date';
import ScanConfig, {FULL_AND_FAST_SCAN_CONFIG_ID} from 'gmp/models/scanconfig';
import {OPENVAS_DEFAULT_SCANNER_ID} from 'gmp/models/scanner';
-import {
- loadEntities as loadAlerts,
- selector as alertSelector,
-} from 'web/store/entities/alerts';
-
import {
loadEntities as loadCredentials,
selector as credentialsSelector,
@@ -70,6 +65,8 @@ import useCapabilities from 'web/utils/useCapabilities';
import EntityComponent from 'web/entity/component';
+import {useLazyGetAlerts} from 'web/graphql/alerts';
+
import {useLazyGetScanners} from 'web/graphql/scanners';
import {useGetScanConfigs} from 'web/graphql/scanconfigs';
@@ -109,6 +106,13 @@ const TaskComponent = props => {
const [stopTask] = useStopTask();
const [resumeTask] = useResumeTask();
+ const [
+ loadAlerts,
+ {alerts, loading: isLoadingAlerts, error: alertError},
+ ] = useLazyGetAlerts({
+ filterString: ALL_FILTER.toFilterString(),
+ });
+
const [
loadScanners,
{scanners, loading: isLoadingScanners, error: scannerError},
@@ -441,7 +445,7 @@ const TaskComponent = props => {
};
const openStandardTaskDialog = task => {
- props.loadAlerts();
+ loadAlerts();
loadScanConfigs();
loadScanners();
props.loadSchedules();
@@ -727,6 +731,11 @@ const TaskComponent = props => {
...state,
error: _('Error while loading targets.'),
}));
+ } else if (alertError) {
+ setDialogState(state => ({
+ ...state,
+ error: _('Error while loading alerts.'),
+ }));
}
// log error all objects to be able to inspect them the console
@@ -739,12 +748,14 @@ const TaskComponent = props => {
if (targetError) {
log.error({targetError});
}
- }, [scanConfigError, scannerError, targetError]);
+
+ if (alertError) {
+ log.error({alertError});
+ }
+ }, [scanConfigError, scannerError, targetError, alertError]);
const {
- alerts,
credentials,
- isLoadingAlerts,
isLoadingSchedules,
isLoadingTags,
schedules,
@@ -1051,14 +1062,12 @@ TaskComponent.propTypes = {
const TAGS_FILTER = ALL_FILTER.copy().set('resource_type', 'task');
const mapStateToProps = rootState => {
- const alertSel = alertSelector(rootState);
const credentialsSel = credentialsSelector(rootState);
const userDefaults = getUserSettingsDefaults(rootState);
const scheduleSel = scheduleSelector(rootState);
const tagsSel = tagsSelector(rootState);
return {
timezone: getTimezone(rootState),
- alerts: alertSel.getEntities(ALL_FILTER),
credentials: credentialsSel.getEntities(ALL_FILTER),
defaultAlertId: userDefaults.getValueByName('defaultalert'),
defaultEsxiCredential: userDefaults.getValueByName('defaultesxicredential'),
@@ -1071,7 +1080,6 @@ const mapStateToProps = rootState => {
defaultSshCredential: userDefaults.getValueByName('defaultsshcredential'),
defaultSmbCredential: userDefaults.getValueByName('defaultsmbcredential'),
defaultTargetId: userDefaults.getValueByName('defaulttarget'),
- isLoadingAlerts: alertSel.isLoadingAllEntities(ALL_FILTER),
isLoadingSchedules: scheduleSel.isLoadingAllEntities(ALL_FILTER),
isLoadingTags: tagsSel.isLoadingAllEntities(ALL_FILTER),
schedules: scheduleSel.getEntities(ALL_FILTER),
@@ -1080,7 +1088,6 @@ const mapStateToProps = rootState => {
};
const mapDispatchToProp = (dispatch, {gmp}) => ({
- loadAlerts: () => dispatch(loadAlerts(gmp)(ALL_FILTER)),
loadCredentials: () => dispatch(loadCredentials(gmp)(ALL_FILTER)),
loadSchedules: () => dispatch(loadSchedules(gmp)(ALL_FILTER)),
loadTags: () => dispatch(loadTags(gmp)(TAGS_FILTER)),
diff --git a/gsa/src/web/utils/testing/index.js b/gsa/src/web/utils/testing/index.js
index b5764934e4..b7049f6860 100644
--- a/gsa/src/web/utils/testing/index.js
+++ b/gsa/src/web/utils/testing/index.js
@@ -177,11 +177,21 @@ export const createGenericQueryMock = (query, result, variables) => {
data: result,
});
- const queryMock = {
- request: {
+ let queryRequest;
+
+ if (hasValue(variables)) {
+ queryRequest = {
query,
variables,
- },
+ };
+ } else {
+ queryRequest = {
+ query,
+ };
+ }
+
+ const queryMock = {
+ request: queryRequest,
newData: resultFunc,
};
return [queryMock, resultFunc];