From 8a7d1b5302b40f3607fa7dfffcf02b701ae72c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:15:45 +0200 Subject: [PATCH 01/16] Update test description The test description wasn't changed after the method got renamed. --- gsa/src/web/store/entities/utils/__tests__/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsa/src/web/store/entities/utils/__tests__/selectors.js b/gsa/src/web/store/entities/utils/__tests__/selectors.js index 9aa10eaecc..20158585b1 100644 --- a/gsa/src/web/store/entities/utils/__tests__/selectors.js +++ b/gsa/src/web/store/entities/utils/__tests__/selectors.js @@ -119,7 +119,7 @@ describe('EntitiesSelector isLoadingEntities tests', () => { }); -describe('EntitiesSelector getIsLoadingEntity tests', () => { +describe('EntitiesSelector isLoadingEntity tests', () => { test('should be false for undefined state', () => { const id = 'a1'; From f0acba725868f3990a02959ad8da4126225812bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:16:36 +0200 Subject: [PATCH 02/16] Update report download api to use camelCase --- gsa/src/gmp/commands/reports.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gsa/src/gmp/commands/reports.js b/gsa/src/gmp/commands/reports.js index 9a9272944c..978f6455af 100644 --- a/gsa/src/gmp/commands/reports.js +++ b/gsa/src/gmp/commands/reports.js @@ -86,12 +86,12 @@ class ReportCommand extends EntityCommand { }); } - download({id}, {report_format_id, delta_report_id, filter}) { + download({id}, {reportFormatId, deltaReportId, filter}) { return this.httpGet({ cmd: 'get_report', - delta_report_id, + delta_report_id: deltaReportId, report_id: id, - report_format_id, + report_format_id: reportFormatId, filter: isDefined(filter) ? filter.all() : ALL_FILTER, }, {transform: DefaultTransform, responseType: 'arraybuffer'}); } From 53797976a7b2f6261dadd3ea6b438d6ba58162ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:16:58 +0200 Subject: [PATCH 03/16] Alwas set ignore_pagination for report get and getDelta commands --- gsa/src/gmp/commands/reports.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gsa/src/gmp/commands/reports.js b/gsa/src/gmp/commands/reports.js index 978f6455af..a48afba258 100644 --- a/gsa/src/gmp/commands/reports.js +++ b/gsa/src/gmp/commands/reports.js @@ -129,6 +129,15 @@ class ReportCommand extends EntityCommand { id, delta_report_id, filter, + ignore_pagination: 1, + }, options).then(this.transformResponse); + } + + get({id}, {filter, ...options} = {}) { + return this.httpGet({ + id, + filter, + ignore_pagination: 1, }, options).then(this.transformResponse); } From 9823550fb42cb32fa53e5092298c0f58a37d8703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:18:17 +0200 Subject: [PATCH 04/16] Use task model function isActive to check if a report is active --- gsa/src/web/pages/reports/row.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/gsa/src/web/pages/reports/row.js b/gsa/src/web/pages/reports/row.js index 1973c40839..ddfa33b52c 100644 --- a/gsa/src/web/pages/reports/row.js +++ b/gsa/src/web/pages/reports/row.js @@ -29,7 +29,7 @@ import {longDate} from 'gmp/locale/date'; import {isDefined} from 'gmp/utils/identity'; -import {TASK_STATUS} from 'gmp/models/task'; +import {TASK_STATUS, isActive} from 'gmp/models/task'; import PropTypes from '../../utils/proptypes.js'; import {renderComponent} from '../../utils/render.js'; @@ -57,12 +57,9 @@ const IconActions = ({ onReportDeltaSelect, }) => { const {report} = entity; - const active = report.scan_run_status !== TASK_STATUS.running && - report.scan_run_status !== TASK_STATUS.requested && - report.scan_run_status !== TASK_STATUS.stoprequested && - report.scan_run_status !== TASK_STATUS.resumerequested; + const scanActive = isActive(report.scan_run_status); - const title = active ? _('Delete Report') : _('Scan is active'); + const title = scanActive ? _('Scan is active') : _('Delete Report'); return ( } ); From 62cb2625e02130020fd781fc056150171c45c674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:21:14 +0200 Subject: [PATCH 05/16] Override loadEntity for report entities store Allow to pass a filter for the results when loading a report. Update report loadEntity tests accordingly. --- .../web/store/entities/__tests__/reports.js | 205 +++++++++++++++++- gsa/src/web/store/entities/reports.js | 18 +- 2 files changed, 219 insertions(+), 4 deletions(-) diff --git a/gsa/src/web/store/entities/__tests__/reports.js b/gsa/src/web/store/entities/__tests__/reports.js index 5823532bb3..dfa1d8a0fd 100644 --- a/gsa/src/web/store/entities/__tests__/reports.js +++ b/gsa/src/web/store/entities/__tests__/reports.js @@ -20,10 +20,209 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {testAll} from '../utils/testing'; +import {isFunction} from 'gmp/utils/identity'; -import * as funcs from '../reports'; +import {types} from 'web/store/entities/utils/actions'; -testAll('report', funcs); +import { + testEntitiesActions, + testEntityActions, + testLoadEntities, + testReducerForEntities, + testReducerForEntity, + createState, + createRootState, +} from 'web/store/entities/utils/testing'; + +import { + entitiesActions, + entityActions, + loadEntities, + loadEntity, + reducer, + deltaEntityActions, + deltaReducer, + deltaSelector, + loadDeltaReport, +} from '../reports'; +import Filter from 'gmp/models/filter'; + +testEntitiesActions('report', entitiesActions); +testEntityActions('report', entityActions); +testLoadEntities('report', loadEntities); +testReducerForEntities('report', reducer, entitiesActions); +testReducerForEntity('report', reducer, entityActions); + +const entityType = 'report'; + +describe('report loadEntity function tests', () => { + + test('should load report successfully', () => { + const id = 'a1'; + const rootState = createState(entityType, { + isLoading: { + [id]: false, + }, + }); + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const get = jest + .fn() + .mockReturnValue(Promise.resolve({ + data: {foo: 'bar'}, + })); + + const gmp = { + [entityType]: { + get, + }, + }; + + expect(loadEntity).toBeDefined(); + expect(isFunction(loadEntity)).toBe(true); + + return loadEntity(gmp)(id)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(get).toBeCalledWith({id}, {filter: undefined}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType, + id, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_SUCCESS, + entityType, + data: {foo: 'bar'}, + id, + }]); + }); + }); + + test('should load report with results filter successfully', () => { + const id = 'a1'; + const rootState = createState(entityType, { + isLoading: { + [id]: false, + }, + }); + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const get = jest + .fn() + .mockReturnValue(Promise.resolve({ + data: {foo: 'bar'}, + })); + + const gmp = { + [entityType]: { + get, + }, + }; + + const filter = Filter.fromString('foo=bar'); + + expect(loadEntity).toBeDefined(); + expect(isFunction(loadEntity)).toBe(true); + + return loadEntity(gmp)(id, filter)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(get).toBeCalledWith({id}, {filter}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType, + id, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_SUCCESS, + entityType, + data: {foo: 'bar'}, + id, + }]); + }); + }); + + test('should not load report if isLoading is true', () => { + const id = 'a1'; + const rootState = createState(entityType, { + isLoading: { + [id]: true, + }, + }); + + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const get = jest + .fn() + .mockReturnValue(Promise.resolve([{id: 'foo'}])); + + const gmp = { + [entityType]: { + get, + }, + }; + + return loadEntity(gmp)(id)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(dispatch).not.toBeCalled(); + expect(get).not.toBeCalled(); + }); + }); + + test('should fail loading entity with an error', () => { + const id = 'a1'; + const rootState = createState(entityType, { + [id]: { + isLoading: false, + }, + }); + + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const get = jest + .fn() + .mockReturnValue(Promise.reject('An Error')); + + const gmp = { + [entityType]: { + get, + }, + }; + + return loadEntity(gmp)(id)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(get).toBeCalledWith({id}, {filter: undefined}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType, + id, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_ERROR, + entityType, + error: 'An Error', + id, + }]); + }); + }); +}); // vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/web/store/entities/reports.js b/gsa/src/web/store/entities/reports.js index 2cc9a41089..784acf2858 100644 --- a/gsa/src/web/store/entities/reports.js +++ b/gsa/src/web/store/entities/reports.js @@ -24,13 +24,29 @@ import {createAll} from './utils/main'; const { loadEntities, - loadEntity, reducer, selector, entitiesActions, entityActions, } = createAll('report'); +const loadEntity = gmp => (id, filter) => (dispatch, getState) => { + const rootState = getState(); + const state = selector(rootState); + + if (state.isLoadingEntity(id)) { + // we are already loading data + return Promise.resolve(); + } + + dispatch(entityActions.request(id)); + + return gmp.report.get({id}, {filter}).then( + response => dispatch(entityActions.success(id, response.data)), + error => dispatch(entityActions.error(id, error)), + ); +}; + export { loadEntities, loadEntity, From 0e11323e2555a76a56f4387d15e5b3b982de85b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:25:33 +0200 Subject: [PATCH 06/16] Add actions, selector, reducer and load function for delta reports Allow to handle delta reports in the redux store. --- .../web/store/entities/__tests__/reports.js | 300 ++++++++++++++++++ gsa/src/web/store/entities/reports.js | 67 ++++ 2 files changed, 367 insertions(+) diff --git a/gsa/src/web/store/entities/__tests__/reports.js b/gsa/src/web/store/entities/__tests__/reports.js index dfa1d8a0fd..c2741d8d32 100644 --- a/gsa/src/web/store/entities/__tests__/reports.js +++ b/gsa/src/web/store/entities/__tests__/reports.js @@ -225,4 +225,304 @@ describe('report loadEntity function tests', () => { }); }); +testEntityActions('deltaReport', deltaEntityActions); +testReducerForEntity('deltaReport', deltaReducer, deltaEntityActions); + +describe('delta report loadDeltaReport function tests', () => { + + test('should load delta report successfully', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + + const rootState = createState('deltaReport', { + isLoading: { + [identifier]: false, + }, + }); + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const getDelta = jest + .fn() + .mockReturnValue(Promise.resolve({ + data: {foo: 'bar'}, + })); + + const gmp = { + [entityType]: { + getDelta, + }, + }; + + expect(loadDeltaReport).toBeDefined(); + expect(isFunction(loadDeltaReport)).toBe(true); + + return loadDeltaReport(gmp)(id, deltaId)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(getDelta).toBeCalledWith({id}, {id: deltaId}, {filter: undefined}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType: 'deltaReport', + id: identifier, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_SUCCESS, + entityType: 'deltaReport', + data: {foo: 'bar'}, + id: identifier, + }]); + }); + }); + + test('should load delta report with results filter successfully', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + + const rootState = createState('deltaReport', { + isLoading: { + [identifier]: false, + }, + }); + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const getDelta = jest + .fn() + .mockReturnValue(Promise.resolve({ + data: {foo: 'bar'}, + })); + + const gmp = { + [entityType]: { + getDelta, + }, + }; + + const filter = Filter.fromString('foo=bar'); + + expect(loadDeltaReport).toBeDefined(); + expect(isFunction(loadDeltaReport)).toBe(true); + + return loadDeltaReport(gmp)(id, deltaId, filter)(dispatch, getState) + .then(() => { + expect(getState).toBeCalled(); + expect(getDelta).toBeCalledWith({id}, {id: deltaId}, {filter}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType: 'deltaReport', + id: identifier, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_SUCCESS, + entityType: 'deltaReport', + data: {foo: 'bar'}, + id: identifier, + }]); + }); + }); + + test('should not load delta report if isLoading is true', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + const rootState = createState('deltaReport', { + isLoading: { + [identifier]: true, + }, + }); + + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const getDelta = jest + .fn() + .mockReturnValue(Promise.resolve([{id: 'foo'}])); + + const gmp = { + [entityType]: { + getDelta, + }, + }; + + return loadDeltaReport(gmp)(id, deltaId)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(dispatch).not.toBeCalled(); + expect(getDelta).not.toBeCalled(); + }); + }); + + test('should fail loading delta report with an error', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + const rootState = createState('deltaReport', { + [identifier]: { + isLoading: false, + }, + }); + + const getState = jest + .fn() + .mockReturnValue(rootState); + + const dispatch = jest.fn(); + + const getDelta = jest + .fn() + .mockReturnValue(Promise.reject('An Error')); + + const gmp = { + [entityType]: { + getDelta, + }, + }; + + return loadDeltaReport(gmp)(id, deltaId)(dispatch, getState).then(() => { + expect(getState).toBeCalled(); + expect(getDelta).toBeCalledWith({id}, {id: deltaId}, {filter: undefined}); + expect(dispatch).toHaveBeenCalledTimes(2); + expect(dispatch.mock.calls[0]).toEqual([{ + type: types.ENTITY_LOADING_REQUEST, + entityType: 'deltaReport', + id: identifier, + }]); + expect(dispatch.mock.calls[1]).toEqual([{ + type: types.ENTITY_LOADING_ERROR, + entityType: 'deltaReport', + error: 'An Error', + id: identifier, + }]); + }); + }); +}); + +describe('deltaSelector isLoading tests', () => { + + test('should be false for undefined state', () => { + const id = 'a1'; + const deltaId = 'a2'; + const rootState = createRootState({}); + const selector = deltaSelector(rootState); + + expect(selector.isLoading(id, deltaId)).toBe(false); + }); + + test('should be false for empty state', () => { + const id = 'a1'; + const deltaId = 'a2'; + const rootState = createState('deltaReport', {}); + const selector = deltaSelector(rootState); + + expect(selector.isLoading(id, deltaId)).toEqual(false); + }); + + test('should be false for unkown id', () => { + const id = 'a1'; + const deltaId = 'a2'; + const rootState = createState('deltaReport', { + isLoading: { + foo: true, + }, + }); + const selector = deltaSelector(rootState); + + expect(selector.isLoading(id, deltaId)).toBe(false); + }); + + test('should be false if false in state', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + const rootState = createState('deltaId', { + isLoading: { + [identifier]: false, + }, + }); + const selector = deltaSelector(rootState); + + expect(selector.isLoading(id, deltaId)).toBe(false); + }); + + test('should be true if true in state', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + const rootState = createState('deltaReport', { + isLoading: { + [identifier]: true, + }, + }); + const selector = deltaSelector(rootState); + + expect(selector.isLoading(id, deltaId)).toBe(true); + }); + +}); + +describe('deltaSelector getEntity tests', () => { + + test('should return undefined for empty state', () => { + const rootState = createRootState({}); + const selector = deltaSelector(rootState); + + expect(selector.getEntity('bar')).toBeUndefined(); + }); + + test('should return undefined for empty byId', () => { + const rootState = createState('deltaReport', { + byId: {}, + }); + const selector = deltaSelector(rootState); + + expect(selector.getEntity('bar')).toBeUndefined(); + }); + + test('should return undefined for unkown id', () => { + const rootState = createState('deltaReport', { + byId: { + foo: { + id: 'foo', + }, + }, + }); + const selector = deltaSelector(rootState); + + expect(selector.getEntity('bar')).toBeUndefined(); + }); + + test('should return entity data', () => { + const id = 'a1'; + const deltaId = 'a2'; + const identifier = `${id}+${deltaId}`; + + const rootState = createState('deltaReport', { + byId: { + [identifier]: { + id: 'bar', + lorem: 'ipsum', + }, + }, + }); + const selector = deltaSelector(rootState); + + expect(selector.getEntity(id, deltaId)).toEqual({ + id: 'bar', + lorem: 'ipsum', + }); + }); +}); + // vim: set ts=2 sw=2 tw=80: diff --git a/gsa/src/web/store/entities/reports.js b/gsa/src/web/store/entities/reports.js index 784acf2858..8e0592979b 100644 --- a/gsa/src/web/store/entities/reports.js +++ b/gsa/src/web/store/entities/reports.js @@ -20,8 +20,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +import {isDefined} from 'gmp/utils/identity'; + +import { + createEntityActions, +} from './utils/actions'; + import {createAll} from './utils/main'; +import {createReducer} from 'web/store/entities/utils/reducers'; + const { loadEntities, reducer, @@ -30,6 +38,41 @@ const { entityActions, } = createAll('report'); +const entityType = 'deltaReport'; + +const deltaIdentifier = (id, deltaId) => `${id}+${deltaId}`; + +class DeltaSelector { + + constructor(state = {}) { + this.state = state; + } + + isLoading(id, deltaId) { + return isDefined(this.state.isLoading) ? + !!this.state.isLoading[deltaIdentifier(id, deltaId)] : + false; + } + + getError(id, deltaId) { + return isDefined(this.state.errors) ? + this.state.errors[deltaIdentifier(id, deltaId)] : + undefined; + } + + getEntity(id, deltaId) { + return isDefined(this.state.byId) ? + this.state.byId[deltaIdentifier(id, deltaId)] : + undefined; + } +}; + +const deltaReducer = createReducer(entityType); +const deltaSelector = rootState => + new DeltaSelector(rootState.entities[entityType]); + +const deltaEntityActions = createEntityActions(entityType); + const loadEntity = gmp => (id, filter) => (dispatch, getState) => { const rootState = getState(); const state = selector(rootState); @@ -47,7 +90,31 @@ const loadEntity = gmp => (id, filter) => (dispatch, getState) => { ); }; +const loadDeltaReport = gmp => (id, deltaId, filter) => + (dispatch, getState) => { + const rootState = getState(); + const state = deltaSelector(rootState); + + if (state.isLoading(id, deltaId)) { + // we are already loading data + return Promise.resolve(); + } + + const identifier = deltaIdentifier(id, deltaId); + + dispatch(deltaEntityActions.request(identifier)); + + return gmp.report.getDelta({id}, {id: deltaId}, {filter}).then( + response => dispatch(deltaEntityActions.success(identifier, response.data)), + error => dispatch(deltaEntityActions.error(identifier, error)), + ); +}; + export { + deltaEntityActions, + deltaReducer, + deltaSelector, + loadDeltaReport, loadEntities, loadEntity, reducer, From aba730e6658369442418ad0adac4739ebcdddbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:26:30 +0200 Subject: [PATCH 07/16] Add the new deltaReport reducer to the store --- gsa/src/web/store/entities/reducers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsa/src/web/store/entities/reducers.js b/gsa/src/web/store/entities/reducers.js index 3d9c5bb2b3..a4b9275307 100644 --- a/gsa/src/web/store/entities/reducers.js +++ b/gsa/src/web/store/entities/reducers.js @@ -41,7 +41,10 @@ import {reducer as override} from './overrides'; import {reducer as permission} from './permissions'; import {reducer as portlist} from './portlists'; import {reducer as reportformat} from './reportformats'; -import {reducer as report} from './reports'; +import { + reducer as report, + deltaReducer as deltaReport, +} from './reports'; import {reducer as result} from './results'; import {reducer as role} from './roles'; import {reducer as scanconfig} from './scanconfigs'; @@ -61,6 +64,7 @@ const entitiesReducer = combineReducers({ cpe, credential, cve, + deltaReport, dfncert, filter, group, From cd638e59b10608b786e097772d454b7007612b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:47:36 +0200 Subject: [PATCH 08/16] Update report details to use redux store for loading data Refactor the report details page to load all data from the redux store. --- gsa/src/web/pages/reports/detailspage.js | 381 +++++++++++------------ 1 file changed, 174 insertions(+), 207 deletions(-) diff --git a/gsa/src/web/pages/reports/detailspage.js b/gsa/src/web/pages/reports/detailspage.js index d465bd6be1..4a6be0abd3 100644 --- a/gsa/src/web/pages/reports/detailspage.js +++ b/gsa/src/web/pages/reports/detailspage.js @@ -26,27 +26,48 @@ import React from 'react'; import {connect} from 'react-redux'; +import {withRouter} from 'react-router-dom'; + import _ from 'gmp/locale'; import logger from 'gmp/log'; -import CancelToken from 'gmp/cancel'; - import {first} from 'gmp/utils/array'; import {isDefined} from 'gmp/utils/identity'; -import {RESET_FILTER, RESULTS_FILTER_FILTER} from 'gmp/models/filter'; +import Filter, {RESET_FILTER, RESULTS_FILTER_FILTER} from 'gmp/models/filter'; import withDownload from 'web/components/form/withDownload'; import withDialogNotification from 'web/components/notification/withDialogNotifiaction'; // eslint-disable-line max-len +import { + loadEntities as loadFilters, + selector as filterSelector, +} from 'web/store/entities/filters'; + +import { + loadEntities as loadReportFormats, + selector as reportFormatsSelector, +} from 'web/store/entities/reportformats'; + +import { + loadDeltaReport, + deltaSelector, + loadEntity as loadReport, + selector as reportSelector, +} from 'web/store/entities/reports'; + +import { + loadEntity as loadTarget, + selector as targetSelector, +} from 'web/store/entities/targets'; + import {renewSessionTimeout} from 'web/store/usersettings/actions'; import {create_pem_certificate} from 'web/utils/cert'; import compose from 'web/utils/compose'; import PropTypes from 'web/utils/proptypes'; -import withCache from 'web/utils/withCache'; import withGmp from 'web/utils/withGmp'; import TargetComponent from '../targets/component'; @@ -56,10 +77,20 @@ import FilterDialog from './detailsfilterdialog'; const log = logger.getLogger('web.pages.report.detailspage'); -const connectFunc = (in_func, out_func) => (...args) => - in_func(...args).then(out_func); +const DEFAULT_FILTER = Filter.fromString('rows=100'); -const DEFAULT_NUM_ROWS = 100; // displayed rows after first request +const REPORT_FORMATS_FILTER = Filter.fromString('active=1 trust=1'); + +const getTarget = (entity = {}) => { + const {report = {}} = entity; + const {task = {}} = report; + return task.target; +}; + +const getFilter = (entity = {}) => { + const {report = {}} = entity; + return report.filter; +}; class ReportDetails extends React.Component { @@ -68,7 +99,6 @@ class ReportDetails extends React.Component { this.state = { activeTab: 1, - filters: [], showFilterDialog: false, }; @@ -98,210 +128,83 @@ class ReportDetails extends React.Component { } componentDidMount() { - const {id, deltaid} = this.props.match.params; - const {filter} = this.props.location.query; + const {filter: filterString} = this.props.location.query; - this.loadInternal({ - id, - delta_id: deltaid, - filter, - reload: true, // reload after initial load from cache - }); + const filter = isDefined(filterString) ? + Filter.fromString(filterString) : undefined; + + this.load(filter); this.loadFilters(); this.loadReportFormats(); } componentWillUnmount() { - this.cancelLastRequest(); this.clearTimer(); } - componentWillReceiveProps(next) { - const {id: old_id, deltaid: old_deltaid} = this.props.match.params; - const {id: next_id, deltaid: next_deltaid} = next.match.params; - - let next_filter; - let old_filter; - - if (isDefined(next.location) && isDefined(next.location.query)) { - next_filter = next.location.query.filter; + componentDidUpdate() { + const {reportFormats} = this.props; + if (!isDefined(this.state.reportFormatId) && isDefined(reportFormats)) { + // set initial report format id + this.setState({reportFormatId: first(reportFormats).id}); } - if (isDefined(this.props.location) && - isDefined(this.props.location.query)) { - old_filter = this.props.location.query.filter; - } - - if (old_id !== next_id || old_deltaid !== next_deltaid || - old_filter !== next_filter) { - - this.setState({activeTab: 1}); - - this.loadInternal({ - id: next_id, - delta_id: next_deltaid, - filter: next_filter, - }); + if (this.state.reportId !== this.props.reportId || + this.state.deltaReportId !== this.props.deltaReportId) { + this.load(); } } - cancelLastRequest() { - if (isDefined(this.cancel)) { - this.cancel(); + load(filter = this.props.filter) { + const {reportId, deltaReportId} = this.props; + + if (!isDefined(filter)) { + filter = DEFAULT_FILTER; } - } - load({ - id = this.state.id, - delta_id = this.state.delta_id, - filter, - force, - } = {}) { - return this.loadInternal({ - id, - delta_id, + log.debug('Loading report', { + reportId, + deltaReportId, filter, - force, }); - } - loadInternal({ - id, - delta_id, - filter, - force = false, - reload = false, - }) { - log.debug('Loading report', id, delta_id, filter); - const {gmp, cache} = this.props; - const {loaded_filter} = this.state; - - this.cancelLastRequest(); - - const token = new CancelToken(cancel => this.cancel = cancel); - - if (isDefined(loaded_filter) && - isDefined(filter) && !loaded_filter.equals(filter)) { - this.setState({ - loading: true, - updating: true, - }); - } - - const options = { - cache, - force, - filter: isDefined(filter) ? filter.all() : undefined, - cancel_token: token, - extra_params: { - ignore_pagination: '1', - }, - }; + this.setState({reportId, deltaReportId}); - let promise; - if (isDefined(delta_id)) { - promise = gmp.report.getDelta({id}, {id: delta_id}, options); - } - else { - promise = gmp.report.get({id}, options); - } - return promise.then(response => { - this.cancel = undefined; - - const {data: entity, meta} = response; - - const {report} = entity; - const {filter: loaded_rfilter} = report; - if (isDefined(filter)) { - filter = filter.mergeExtraKeywords(loaded_rfilter); - } - else { - filter = loaded_rfilter.set('rows', DEFAULT_NUM_ROWS); - } - - log.debug('Loaded report', entity); - - this.setState({ - entity, - filter, - loaded_filter: filter, - id, - delta_id, - loading: false, - updating: false, - }); - - if (meta.fromcache && (meta.dirty || reload)) { - log.debug('Forcing reload of report', meta.dirty); - this.startTimer(true); - } - else { - this.startTimer(); - } - }) - .catch(err => { - if (isDefined(err.isCancel) && err.isCancel()) { - return; - } - - const rej = this.handleError(err); - this.setState({ - entity: undefined, - loading: false, - }); - return rej; - }); + this.props.loadReport(reportId, deltaReportId, filter); } loadReportFormats() { - const {gmp} = this.props; - - gmp.reportformats.getAll().then(response => { - let {data: report_formats} = response; - report_formats = report_formats.filter(format => { - return format.isActive() && - (format.isTrusted() || format.isPredefined()); - }); - const report_format_id = first(report_formats).id; - this.setState({ - report_formats, - report_format_id, - }); - }); + this.props.loadReportFormats(); } loadFilters() { - const {gmp} = this.props; - - gmp.filters.getAll({filter: RESULTS_FILTER_FILTER}).then(response => { - this.setState({filters: response.data}); - }); + this.props.loadFilters(); } reload() { // reload data from backend - this.load({force: true, filter: this.state.filter}); + this.load(); } - getRefreshInterval() { + getReloadInterval() { const {gmp} = this.props; return gmp.reloadInterval; } - startTimer(immediate = false) { - const refresh = immediate ? 0 : this.getRefreshInterval(); + startTimer() { + const interval = this.getReloadInterval(); - if (refresh >= 0) { - this.timer = window.setTimeout(this.handleTimer, refresh); + if (interval > 0) { + this.timer = global.setTimeout(this.handleTimer, interval); log.debug('Started reload timer with id', this.timer, 'and interval of', - refresh, 'milliseconds'); + interval, 'milliseconds'); } } clearTimer() { if (isDefined(this.timer)) { log.debug('Clearing reload timer with id', this.timer); - window.clearTimeout(this.timer); + global.clearTimeout(this.timer); } } @@ -325,15 +228,15 @@ class ReportDetails extends React.Component { handleFilterChange(filter) { this.handleInteraction(); - this.load({filter}); + this.load(filter); } handleFilterRemoveClick() { this.handleFilterChange(RESET_FILTER); } - handleFilterResetClick(filter) { - this.handleFilterChange(); + handleFilterResetClick() { + this.handleFilterChange(Filter.fromString('')); } handleActivateTab(index) { @@ -343,7 +246,7 @@ class ReportDetails extends React.Component { } handleReportFormatChange(value) { - this.setState({report_format_id: value}); + this.setState({reportFormatId: value}); } handleAddToAssets() { @@ -385,17 +288,20 @@ class ReportDetails extends React.Component { } handleReportDownload() { - const {gmp, onDownload} = this.props; const { + deltaReportId, entity, filter, - delta_id, - report_format_id, - report_formats, + gmp, + reportFormats = [], + onDownload, + } = this.props; + const { + reportFormatId, } = this.state; - const report_format = report_formats.find( - format => report_format_id === format.id); + const report_format = reportFormats.find( + format => reportFormatId === format.id); const extension = isDefined(report_format) ? report_format.extension : 'unknown'; // unknown should never happen but we should be save here @@ -403,8 +309,8 @@ class ReportDetails extends React.Component { this.handleInteraction(); gmp.report.download(entity, { - report_format_id, - delta_report_id: delta_id, + reportFormatId, + deltaReportId, filter, }).then(response => { const {data} = response; @@ -429,12 +335,12 @@ class ReportDetails extends React.Component { handleFilterCreated(filter) { this.handleInteraction(); - this.load({filter}); + this.load(filter); this.loadFilters(); } handleFilterAddLogLevel() { - const {filter} = this.state; + const {filter} = this.props; let levels = filter.get('levels', ''); this.handleInteraction(); @@ -443,31 +349,31 @@ class ReportDetails extends React.Component { levels += 'g'; const lfilter = filter.copy(); lfilter.set('levels', levels); - this.load({filter: lfilter}); + this.load(lfilter); } } handleFilterRemoveSeverity() { - const {filter} = this.state; + const {filter} = this.props; this.handleInteraction(); if (filter.has('severity')) { const lfilter = filter.copy(); lfilter.delete('severity'); - this.load({filter: lfilter}); + this.load(lfilter); } } handleFilterDecreaseMinQoD() { - const {filter} = this.state; + const {filter} = this.props; this.handleInteraction(); if (filter.has('min_qod')) { const lfilter = filter.copy(); lfilter.set('min_qod', 30); - this.load({filter: lfilter}); + this.load(lfilter); } } @@ -479,23 +385,32 @@ class ReportDetails extends React.Component { } loadTarget() { - const {entity} = this.state; - const {gmp} = this.props; - const {report} = entity; - const {task} = report; - const {target} = task; + const {entity} = this.props; + const {target} = getTarget(entity); - return gmp.target.get(target).then(response => response.data); + return this.props.loadTarget(target.id); } render() { - const {onInteraction} = this.props; const { + entity, filter, - entity = {}, - showFilterDialog = false, + filters = [], + isLoading, + reportFormats, + target, + onInteraction, + showError, + showErrorMessage, + showSuccessMessage, + } = this.props; + const { + activeTab, + reportFormatId, + showFilterDialog, } = this.state; - const {report} = entity; + + const {report} = entity || {}; return ( {({edit}) => ( this.loadTarget() + .then(() => edit(target))} + onTlsCertificateDownloadClick={this.handleTlsCertificateDownload} + showError={showError} + showErrorMessage={showErrorMessage} + showSuccessMessage={showSuccessMessage} /> )} @@ -541,27 +465,70 @@ class ReportDetails extends React.Component { } ReportDetails.propTypes = { - cache: PropTypes.cache.isRequired, + deltaReportId: PropTypes.id, + entity: PropTypes.model, + filter: PropTypes.filter, + filters: PropTypes.array, gmp: PropTypes.gmp.isRequired, + isLoading: PropTypes.bool.isRequired, + loadFilters: PropTypes.func.isRequired, + loadReport: PropTypes.func.isRequired, + loadReportFormats: PropTypes.func.isRequired, + loadTarget: PropTypes.func.isRequired, location: PropTypes.object.isRequired, match: PropTypes.object.isRequired, + reportFormats: PropTypes.array, + reportId: PropTypes.id, showError: PropTypes.func.isRequired, showErrorMessage: PropTypes.func.isRequired, showSuccessMessage: PropTypes.func.isRequired, + target: PropTypes.model, onDownload: PropTypes.func.isRequired, onInteraction: PropTypes.func.isRequired, }; -const mapDispatchToProps = (dispatch, {gmp}) => ({ - onInteraction: () => dispatch(renewSessionTimeout(gmp)()), -}); +const mapDispatchToProps = (dispatch, {gmp}) => { + return { + onInteraction: () => dispatch(renewSessionTimeout(gmp)()), + loadFilters: () => dispatch(loadFilters(gmp)(RESULTS_FILTER_FILTER)), + loadTarget: targetId => dispatch(loadTarget(gmp)(targetId)), + loadReportFormats: () => dispatch( + loadReportFormats(gmp)(REPORT_FORMATS_FILTER)), + loadReport: (id, deltaId, filter) => dispatch(isDefined(deltaId) ? + loadDeltaReport(gmp)(id, deltaId, filter) : + loadReport(gmp)(id, filter)), + }; +}; + +const mapStateToProps = (rootState, {match}) => { + const {id, deltaid} = match.params; + const filterSel = filterSelector(rootState); + const targetSel = targetSelector(rootState); + const reportSel = reportSelector(rootState); + const deltaSel = deltaSelector(rootState); + const reportFormatsSel = reportFormatsSelector(rootState); + + const entity = isDefined(deltaid) ? deltaSel.getEntity(id, deltaid) : + reportSel.getEntity(id); + const target = getTarget(entity); + return { + entity, + filter: getFilter(entity), + isLoading: !isDefined(entity), + filters: filterSel.getEntities(RESULTS_FILTER_FILTER), + target: isDefined(target) ? targetSel.getEntity(target.id) : undefined, + reportFormats: reportFormatsSel.getEntities(REPORT_FORMATS_FILTER), + reportId: id, + deltaReportId: deltaid, + }; +}; export default compose( - withCache({cache: 'report'}), + withRouter, withGmp, withDialogNotification, withDownload, - connect(undefined, mapDispatchToProps), + connect(mapStateToProps, mapDispatchToProps), )(ReportDetails); // vim: set ts=2 sw=2 tw=80: From c49492fd22388356d16a35bb4b34cb506f69e357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 12:51:53 +0200 Subject: [PATCH 09/16] Update report details page content Use new camelCase data from the report details page at report details page content. --- gsa/src/web/pages/reports/detailscontent.js | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/gsa/src/web/pages/reports/detailscontent.js b/gsa/src/web/pages/reports/detailscontent.js index 931d5cafbc..1696555b5e 100644 --- a/gsa/src/web/pages/reports/detailscontent.js +++ b/gsa/src/web/pages/reports/detailscontent.js @@ -251,13 +251,12 @@ const PageContent = ({ entity, filter, filters, - loading = true, - report_formats, - report_format_id, + isLoading = true, + reportFormats, + reportFormatId, showError, showErrorMessage, showSuccessMessage, - updating = false, onActivateTab, onAddToAssetsClick, onTlsCertificateDownloadClick, @@ -301,7 +300,6 @@ const PageContent = ({ } = report; const hasReport = isDefined(entity); - loading = loading && (!hasReport || updating); const delta = isDefined(report.isDeltaReport) ? report.isDeltaReport() : undefined; @@ -314,7 +312,7 @@ const PageContent = ({ {_('Report:')} - {loading ? + {isLoading ? {_('Loading')} : @@ -355,10 +353,10 @@ const PageContent = ({ - {loading ? + {isLoading ? : Date: Fri, 7 Sep 2018 12:53:29 +0200 Subject: [PATCH 10/16] Remove filter instead of resetting it Resetting meand using the default results filter which may still not show all results. Therefore allow the user to remove the current applied filter completely. --- gsa/src/web/pages/reports/detailscontent.js | 2 +- gsa/src/web/pages/reports/emptyresultsreport.js | 8 ++++---- gsa/src/web/pages/reports/resultstab.js | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gsa/src/web/pages/reports/detailscontent.js b/gsa/src/web/pages/reports/detailscontent.js index 1696555b5e..1f6b3687c6 100644 --- a/gsa/src/web/pages/reports/detailscontent.js +++ b/gsa/src/web/pages/reports/detailscontent.js @@ -499,7 +499,7 @@ const PageContent = ({ onFilterDecreaseMinQoDClick={onFilterDecreaseMinQoDClick} onFilterRemoveSeverityClick={onFilterRemoveSeverityClick} onFilterEditClick={onFilterEditClick} - onFilterResetClick={onFilterResetClick} + onFilterRemoveClick={onFilterRemoveClick} onInteraction={onInteraction} onTargetEditClick={onTargetEditClick} /> diff --git a/gsa/src/web/pages/reports/emptyresultsreport.js b/gsa/src/web/pages/reports/emptyresultsreport.js index 8a00ebc3bd..856dadc703 100644 --- a/gsa/src/web/pages/reports/emptyresultsreport.js +++ b/gsa/src/web/pages/reports/emptyresultsreport.js @@ -44,7 +44,7 @@ const EmptyResultsReport = ({ onFilterEditClick, onFilterDecreaseMinQoDClick, onFilterRemoveSeverityClick, - onFilterResetClick, + onFilterRemoveClick, }) => { const levels = filter.get('levels', ''); const severity = filter.getTerm('severity'); @@ -111,9 +111,9 @@ const EmptyResultsReport = ({ - {_('Reset the filter settings to the defaults.')} + {_('Remove all filter settings.')} @@ -126,8 +126,8 @@ EmptyResultsReport.propTypes = { onFilterAddLogLevelClick: PropTypes.func.isRequired, onFilterDecreaseMinQoDClick: PropTypes.func.isRequired, onFilterEditClick: PropTypes.func.isRequired, + onFilterRemoveClick: PropTypes.func.isRequired, onFilterRemoveSeverityClick: PropTypes.func.isRequired, - onFilterResetClick: PropTypes.func.isRequired, }; export default EmptyResultsReport; diff --git a/gsa/src/web/pages/reports/resultstab.js b/gsa/src/web/pages/reports/resultstab.js index 1176b7b179..babd6f796a 100644 --- a/gsa/src/web/pages/reports/resultstab.js +++ b/gsa/src/web/pages/reports/resultstab.js @@ -45,7 +45,7 @@ const ResultsTab = ({ onFilterDecreaseMinQoDClick, onFilterEditClick, onFilterRemoveSeverityClick, - onFilterResetClick, + onFilterRemoveClick, onInteraction, onTargetEditClick, }) => { @@ -68,8 +68,8 @@ const ResultsTab = ({ onFilterAddLogLevelClick={onFilterAddLogLevelClick} onFilterDecreaseMinQoDClick={onFilterDecreaseMinQoDClick} onFilterEditClick={onFilterEditClick} + onFilterRemoveClick={onFilterRemoveClick} onFilterRemoveSeverityClick={onFilterRemoveSeverityClick} - onFilterResetClick={onFilterResetClick} /> ); } @@ -104,8 +104,8 @@ ResultsTab.propTypes = { onFilterAddLogLevelClick: PropTypes.func.isRequired, onFilterDecreaseMinQoDClick: PropTypes.func.isRequired, onFilterEditClick: PropTypes.func.isRequired, + onFilterRemoveClick: PropTypes.func.isRequired, onFilterRemoveSeverityClick: PropTypes.func.isRequired, - onFilterResetClick: PropTypes.func.isRequired, onInteraction: PropTypes.func.isRequired, onTargetEditClick: PropTypes.func.isRequired, }; From 3b4fa1a39750d0d175e31fdd85e91f1c2ed425aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 13:14:50 +0200 Subject: [PATCH 11/16] Fix opening target edit dialog from report details It's not possible to get the target from the store directly after loading it into the store when opening the edit dialog. The correct target isn't passed to the details page before the dialog is open. Therefore bypass the store and load the target directly. --- gsa/src/web/pages/reports/detailspage.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/gsa/src/web/pages/reports/detailspage.js b/gsa/src/web/pages/reports/detailspage.js index 4a6be0abd3..d42592b71f 100644 --- a/gsa/src/web/pages/reports/detailspage.js +++ b/gsa/src/web/pages/reports/detailspage.js @@ -58,11 +58,6 @@ import { selector as reportSelector, } from 'web/store/entities/reports'; -import { - loadEntity as loadTarget, - selector as targetSelector, -} from 'web/store/entities/targets'; - import {renewSessionTimeout} from 'web/store/usersettings/actions'; import {create_pem_certificate} from 'web/utils/cert'; @@ -386,7 +381,7 @@ class ReportDetails extends React.Component { loadTarget() { const {entity} = this.props; - const {target} = getTarget(entity); + const target = getTarget(entity); return this.props.loadTarget(target.id); } @@ -398,7 +393,6 @@ class ReportDetails extends React.Component { filters = [], isLoading, reportFormats, - target, onInteraction, showError, showErrorMessage, @@ -443,7 +437,7 @@ class ReportDetails extends React.Component { onReportFormatChange={this.handleReportFormatChange} onTagSuccess={this.handleChanged} onTargetEditClick={() => this.loadTarget() - .then(() => edit(target))} + .then(response => edit(response.data))} onTlsCertificateDownloadClick={this.handleTlsCertificateDownload} showError={showError} showErrorMessage={showErrorMessage} @@ -491,7 +485,7 @@ const mapDispatchToProps = (dispatch, {gmp}) => { return { onInteraction: () => dispatch(renewSessionTimeout(gmp)()), loadFilters: () => dispatch(loadFilters(gmp)(RESULTS_FILTER_FILTER)), - loadTarget: targetId => dispatch(loadTarget(gmp)(targetId)), + loadTarget: targetId => gmp.target.get({id: targetId}), loadReportFormats: () => dispatch( loadReportFormats(gmp)(REPORT_FORMATS_FILTER)), loadReport: (id, deltaId, filter) => dispatch(isDefined(deltaId) ? @@ -503,20 +497,17 @@ const mapDispatchToProps = (dispatch, {gmp}) => { const mapStateToProps = (rootState, {match}) => { const {id, deltaid} = match.params; const filterSel = filterSelector(rootState); - const targetSel = targetSelector(rootState); const reportSel = reportSelector(rootState); const deltaSel = deltaSelector(rootState); const reportFormatsSel = reportFormatsSelector(rootState); const entity = isDefined(deltaid) ? deltaSel.getEntity(id, deltaid) : reportSel.getEntity(id); - const target = getTarget(entity); return { entity, filter: getFilter(entity), isLoading: !isDefined(entity), filters: filterSel.getEntities(RESULTS_FILTER_FILTER), - target: isDefined(target) ? targetSel.getEntity(target.id) : undefined, reportFormats: reportFormatsSel.getEntities(REPORT_FORMATS_FILTER), reportId: id, deltaReportId: deltaid, From a546dc3a9c9e89e6fefed09a6131bc919c44e4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 13:17:14 +0200 Subject: [PATCH 12/16] Reset the report filter after delta report selection If the filter isn't reset the report list would keep the task_id filter when visiting the report list again. --- gsa/src/web/pages/reports/listpage.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gsa/src/web/pages/reports/listpage.js b/gsa/src/web/pages/reports/listpage.js index b3724bd1dd..0da3f6be8c 100644 --- a/gsa/src/web/pages/reports/listpage.js +++ b/gsa/src/web/pages/reports/listpage.js @@ -174,17 +174,26 @@ class Page extends React.Component { } handleReportDeltaSelect(report) { - const {selectedDeltaReport} = this.state; + const {onFilterChanged} = this.props; + const {selectedDeltaReport, beforeSelectFilter} = this.state; if (isDefined(selectedDeltaReport)) { const {history} = this.props; + + onFilterChanged(beforeSelectFilter); + history.push('/report/delta/' + selectedDeltaReport.id + '/' + report.id); } else { - const {onFilterChanged, filter = new Filter()} = this.props; + const {filter = new Filter()} = this.props; + onFilterChanged(filter.copy().set('task_id', report.task.id)); - this.setState({selectedDeltaReport: report}); + + this.setState({ + beforeSelectFilter: filter, + selectedDeltaReport: report, + }); } } From 3da2444f585564f974efcd76803ee422c5b1857f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 13:59:16 +0200 Subject: [PATCH 13/16] Move LOAD_TIME_FACTOR to web/utils/constants.js --- gsa/src/web/entities/container.js | 3 +-- gsa/src/web/utils/constants.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gsa/src/web/entities/container.js b/gsa/src/web/entities/container.js index f982c0bf0d..4ce474d6dd 100644 --- a/gsa/src/web/entities/container.js +++ b/gsa/src/web/entities/container.js @@ -41,6 +41,7 @@ import Filter, {RESET_FILTER} from 'gmp/models/filter'; import {YES_VALUE} from 'gmp/parser'; +import {LOAD_TIME_FACTOR} from 'web/utils/constants'; import PropTypes from 'web/utils/proptypes'; import SelectionType from 'web/utils/selectiontype'; @@ -60,8 +61,6 @@ const exclude_props = [ 'onDownload', ]; -const LOAD_TIME_FACTOR = 1.2; - class EntitiesContainer extends React.Component { constructor(...args) { diff --git a/gsa/src/web/utils/constants.js b/gsa/src/web/utils/constants.js index 9d213d254c..26831f57ae 100644 --- a/gsa/src/web/utils/constants.js +++ b/gsa/src/web/utils/constants.js @@ -22,4 +22,6 @@ */ export const DEFAULT_RELOAD_INTERVAL_ACTIVE = 3 * 1000; // three seconds +export const LOAD_TIME_FACTOR = 1.2; + // vim: set ts=2 sw=2 tw=80: From 6f133e0b9c287c2a7916ad97cb72ba192b5611ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 14:01:50 +0200 Subject: [PATCH 14/16] Don't allow starting timer if the component is unmounted --- gsa/src/web/pages/reports/detailspage.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gsa/src/web/pages/reports/detailspage.js b/gsa/src/web/pages/reports/detailspage.js index d42592b71f..a34e423d81 100644 --- a/gsa/src/web/pages/reports/detailspage.js +++ b/gsa/src/web/pages/reports/detailspage.js @@ -123,6 +123,7 @@ class ReportDetails extends React.Component { } componentDidMount() { + this.isRunning = true; const {filter: filterString} = this.props.location.query; const filter = isDefined(filterString) ? @@ -134,6 +135,8 @@ class ReportDetails extends React.Component { } componentWillUnmount() { + this.isRunning = false; + this.clearTimer(); } @@ -187,6 +190,10 @@ class ReportDetails extends React.Component { } startTimer() { + if (!this.isRunning) { + return; + } + const interval = this.getReloadInterval(); if (interval > 0) { From b124028c9762d414067c7ac184f5230f06f91c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Fri, 7 Sep 2018 14:02:29 +0200 Subject: [PATCH 15/16] Implement reloading reports if report is active Automatically reload the report if it is active. --- gsa/src/web/pages/reports/detailspage.js | 42 +++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/gsa/src/web/pages/reports/detailspage.js b/gsa/src/web/pages/reports/detailspage.js index a34e423d81..b7a69a405a 100644 --- a/gsa/src/web/pages/reports/detailspage.js +++ b/gsa/src/web/pages/reports/detailspage.js @@ -32,6 +32,8 @@ import _ from 'gmp/locale'; import logger from 'gmp/log'; +import {isActive} from 'gmp/models/task'; + import {first} from 'gmp/utils/array'; import {isDefined} from 'gmp/utils/identity'; @@ -61,6 +63,10 @@ import { import {renewSessionTimeout} from 'web/store/usersettings/actions'; import {create_pem_certificate} from 'web/utils/cert'; +import { + DEFAULT_RELOAD_INTERVAL_ACTIVE, + LOAD_TIME_FACTOR, +} from 'web/utils/constants'; import compose from 'web/utils/compose'; import PropTypes from 'web/utils/proptypes'; import withGmp from 'web/utils/withGmp'; @@ -153,6 +159,20 @@ class ReportDetails extends React.Component { } } + startMeasurement() { + this.startTimeStamp = performance.now(); + } + + endMeasurement() { + if (!isDefined(this.startTimeStamp)) { + return 0; + } + + const duration = performance.now() - this.startTimeStamp; + this.startTimeStamp = undefined; + return duration; + } + load(filter = this.props.filter) { const {reportId, deltaReportId} = this.props; @@ -166,9 +186,12 @@ class ReportDetails extends React.Component { filter, }); + this.startMeasurement(); + this.setState({reportId, deltaReportId}); - this.props.loadReport(reportId, deltaReportId, filter); + this.props.loadReport(reportId, deltaReportId, filter) + .then(() => this.startTimer()); } loadReportFormats() { @@ -185,8 +208,10 @@ class ReportDetails extends React.Component { } getReloadInterval() { - const {gmp} = this.props; - return gmp.reloadInterval; + const {entity} = this.props; + return isActive(entity.report.scan_run_status) ? + DEFAULT_RELOAD_INTERVAL_ACTIVE : + 0; // report doesn't change anymore. no need to reload } startTimer() { @@ -194,7 +219,16 @@ class ReportDetails extends React.Component { return; } - const interval = this.getReloadInterval(); + const loadTime = this.endMeasurement(); + + log.debug('Loading time was', loadTime, 'milliseconds'); + + let interval = this.getReloadInterval(); + + if (loadTime > interval && interval !== 0) { + // ensure timer is longer then the loading procedure + interval = loadTime * LOAD_TIME_FACTOR; + } if (interval > 0) { this.timer = global.setTimeout(this.handleTimer, interval); From ebd76669b97e16e3b1eb10cac87f2b254f59d108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Tue, 11 Sep 2018 08:06:39 +0200 Subject: [PATCH 16/16] Rename measurement methods Rename start/endMeasurement to start/endDurationMeasurement --- gsa/src/web/pages/reports/detailspage.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gsa/src/web/pages/reports/detailspage.js b/gsa/src/web/pages/reports/detailspage.js index b7a69a405a..8cce478539 100644 --- a/gsa/src/web/pages/reports/detailspage.js +++ b/gsa/src/web/pages/reports/detailspage.js @@ -159,11 +159,11 @@ class ReportDetails extends React.Component { } } - startMeasurement() { + startDurationMeasurement() { this.startTimeStamp = performance.now(); } - endMeasurement() { + endDurationMeasurement() { if (!isDefined(this.startTimeStamp)) { return 0; } @@ -186,7 +186,7 @@ class ReportDetails extends React.Component { filter, }); - this.startMeasurement(); + this.startDurationMeasurement(); this.setState({reportId, deltaReportId}); @@ -219,7 +219,7 @@ class ReportDetails extends React.Component { return; } - const loadTime = this.endMeasurement(); + const loadTime = this.endDurationMeasurement(); log.debug('Loading time was', loadTime, 'milliseconds');