Skip to content

Commit

Permalink
Add feedback notification for feed sync (#4120)
Browse files Browse the repository at this point in the history
* ADD: notification when feed is syncing

* add test
  • Loading branch information
daniele-mng authored Sep 5, 2024
1 parent 07f468f commit 49657d7
Show file tree
Hide file tree
Showing 26 changed files with 805 additions and 86 deletions.
1 change: 1 addition & 0 deletions allowedSnakeCase.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ module.exports = [
'start_minute',
'start_time',
'start_timezone',
'sync_not_available',
'subgroup_column',
'subject_dn',
'subject_type',
Expand Down
7 changes: 7 additions & 0 deletions public/locales/gsa-de.json
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@
"Diffuse": "Diffus",
"Disable Tag": "Tag deaktivieren",
"Distance": "Entfernung",
"Documentation": "Dokumentation",
"Do not automatically delete reports": "Berichte nicht automatisch löschen",
"Do not change": "Nicht ändern",
"Do not start automatically": "Nicht automatisch starten",
Expand Down Expand Up @@ -582,6 +583,7 @@
"Entire Operation": "Gesamte Ausführung",
"Entity": "Objekt",
"Error": "Fehler",
"Error fetching the feed": "Fehler beim Abrufen des Feeds",
"Error Message": "Fehlermeldung",
"Error Messages": "Fehlermeldungen",
"Error while loading Report {{reportId}}": "Fehler beim Laden des Berichts {{reportId}}",
Expand Down Expand Up @@ -674,6 +676,8 @@
"Families: Trend": "Familien: Trend",
"Family": "Familie",
"Feed Status": "Feed-Status",
"Feed is currently syncing.": "Der Feed wird derzeit synchronisiert.",
"Feed is currently syncing. Please try again later.": "Der Feed wird derzeit synchronisiert. Bitte versuchen Sie es später erneut.",
"Feeds": "Feeds",
"File path": "Dateipfad",
"Filter": "Filter",
Expand Down Expand Up @@ -1198,6 +1202,7 @@
"Please note that assigning a tag to {{count}} items may take several minutes.": "Bitte beachten Sie, dass es einige Minuten dauern kann, einen Tag {{count}} Objekten zuzuordnen.",
"Please note: You are about to change your own personal user data as Super Admin! It is not possible to change the login name. If you have modified the login name, neither the login name nor any other changes made will be saved. If you have made any modifications other than the login name, the data will be saved when clicking OK, and you will be logged out immediately.": "Bitte beachten Sie: Sie sind dabei Ihre eigenen persönlichen Nutzerdaten als Super Administrator zu ändern! Es ist nicht möglich, den Loginnamen zu ändern. Wenn Sie den Loginnamen modifiziert haben, werden weder der Loginname noch jedwede andere Änderungen gespeichert. Wenn Sie andere Änderungen vorgenommen haben, werden die Daten beim Klick auf OK gespeichert und Sie werden umgehend ausgeloggt.",
"Please note: You are about to create a user without a role. This user will not have any permissions and as a result will not be able to login.": "Bitte beachten Sie: Sie sind dabei einen Benutzer ohne Rolle zu erstellen. Dieser Benutzer wird keine Berechtigungen haben und deshalb nicht in der Lage sein, sich einzuloggen.",
"Please wait while the feed is syncing. Scans are not available during this time. For more information, visit the": "Bitte warten Sie, während der Feed synchronisiert wird. Scans sind während dieser Zeit nicht verfügbar. Für weitere Informationen besuchen Sie die",
"Please try again.": "Bitte versuchen Sie es erneut.",
"POC": "POC",
"Policies": "Richtlinien",
Expand Down Expand Up @@ -1536,6 +1541,7 @@
"Support for RADIUS is not available.": "Unterstützung für RADIUS ist nicht verfügbar.",
"System Logger": "System-Logger",
"System Reports": "Systemberichte",
"Synchronization issue: {{error}}": "Synchronisationsproblem: {{error}}",
"TCP": "TCP",
"TCP Port Count": "TCP-Portanzahl",
"TLS Certificate": "TLS-Zertifikat",
Expand Down Expand Up @@ -1693,6 +1699,7 @@
"There are notes for this result": "Es gibt Notizen zu diesem Ergebnis",
"There are overrides for this result": "Es gibt Übersteuerungen zu diesem Ergebnis",
"There are tickets for this result": "Es gibt Tickets zu diesem Ergebnis",
"There was an error fetching the feed. It will be retried in a few minutes.": "Beim Abrufen des Feeds ist ein Fehler aufgetreten. Es wird in wenigen Minuten erneut versucht.",
"There may be results below the currently selected Quality of Detection (QoD).": "Es könnten Ergebnisse vorliegen, die unterhalb der aktuellen Grenze für die Qualität der Erkennung (QdE) liegen.",
"Third Party": "Drittanbieter",
"This CPE does not appear in the CPE dictionary but is referenced by one or more CVE.": "Diese CPE ist nicht im CPE-Dictionary, wird aber von einem oder mehreren CVE referenziert.",
Expand Down
69 changes: 68 additions & 1 deletion src/gmp/commands/__tests__/feedstatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,79 @@ describe('FeedStatusCommand tests', () => {
});

const {data} = resp;
expect(data[0].feed_type).toEqual('NVT');
expect(data[0].feedType).toEqual('NVT');
expect(data[0].name).toEqual('foo');
expect(data[0].description).toEqual('bar');
expect(data[0].currentlySyncing).toEqual({timestamp: 'baz'});
expect(data[0].status).toEqual(undefined);
expect(data[0].version).toEqual('20190625T1319');
});
});

test('should return isSyncing true when feeds are currently syncing', async () => {
const response = createResponse({
get_feeds: {
get_feeds_response: {
feed: [
{type: 'NVT', currently_syncing: true, sync_not_available: false},
{type: 'SCAP', currently_syncing: false, sync_not_available: false},
],
},
},
});

const fakeHttp = createHttp(response);
const cmd = new FeedStatus(fakeHttp);

const result = await cmd.checkFeedSync();
expect(result.isSyncing).toBe(true);
});

test('should return isSyncing true when feeds are not present', async () => {
const response = createResponse({
get_feeds: {
get_feeds_response: {
feed: [
{
type: 'OTHER',
currently_syncing: false,
sync_not_available: false,
},
],
},
},
});

const fakeHttp = createHttp(response);
const cmd = new FeedStatus(fakeHttp);

const result = await cmd.checkFeedSync();
expect(result.isSyncing).toBe(true);
});

test('should return isSyncing false when feeds are not syncing and are present', async () => {
const response = createResponse({
get_feeds: {
get_feeds_response: {
feed: [
{type: 'NVT', currently_syncing: false, sync_not_available: false},
{type: 'SCAP', currently_syncing: false, sync_not_available: false},
],
},
},
});

const fakeHttp = createHttp(response);
const cmd = new FeedStatus(fakeHttp);

const result = await cmd.checkFeedSync();
expect(result.isSyncing).toBe(false);
});

test('should throw an error when readFeedInformation fails', async () => {
const fakeHttp = createHttp(Promise.reject(new Error('Network error')));
const cmd = new FeedStatus(fakeHttp);

await expect(cmd.checkFeedSync()).rejects.toThrow('Network error');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
import {describe, test, expect} from '@gsa/testing';

import {TaskCommand} from '../tasks';
import {FeedStatus} from '../feedstatus';

import {createActionResultResponse, createHttp} from '../testing';
import {
createActionResultResponse,
createHttp,
createResponse,
} from '../testing';
import {
HOSTS_ORDERING_RANDOM,
AUTO_DELETE_KEEP_DEFAULT_VALUE,
Expand Down Expand Up @@ -280,4 +285,29 @@ describe('TaskCommand tests', () => {
expect(data.id).toEqual('foo');
});
});

test('should throw an error if feed is currently syncing', async () => {
const response = createResponse({
get_feeds: {
get_feeds_response: {
feed: [
{type: 'NVT', currently_syncing: true, sync_not_available: false},
{type: 'SCAP', currently_syncing: false, sync_not_available: false},
],
},
},
});
const fakeHttp = createHttp(response);

const taskCmd = new TaskCommand(fakeHttp);

const feedCmd = new FeedStatus(fakeHttp);

const result = await feedCmd.checkFeedSync();
expect(result.isSyncing).toBe(true);

await expect(taskCmd.start({id: 'task1'})).rejects.toThrow(
'Feed is currently syncing. Please try again later.',
);
});
});
60 changes: 44 additions & 16 deletions src/gmp/commands/feedstatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ export const CERT_FEED = 'CERT';
export const SCAP_FEED = 'SCAP';
export const GVMD_DATA_FEED = 'GVMD_DATA';

export class Feed {
constructor({type, name, description, status, version, currently_syncing}) {
this.feed_type = type;
this.name = name;
this.description = description;
this.status = status;
this.currentlySyncing = currently_syncing;

const versionDate = convertVersion(version);
this.version = versionDate;

const lastUpdate = parseDate(versionDate);
this.age = duration(date().diff(lastUpdate));
}
export function createFeed(feed) {
const versionDate = convertVersion(feed.version);
const lastUpdate = parseDate(versionDate);

return {
feedType: feed.type,
name: feed.name,
description: feed.description,
status: feed.status,
currentlySyncing: feed.currently_syncing,
syncNotAvailable: feed.sync_not_available,
version: versionDate,
age: duration(date().diff(lastUpdate)),
};
}

export class FeedStatus extends HttpCommand {
Expand All @@ -45,13 +45,41 @@ export class FeedStatus extends HttpCommand {
readFeedInformation() {
return this.httpGet().then(response => {
const {data: envelope} = response;
const {get_feeds_response: feedsresponse} = envelope.get_feeds;
const {get_feeds_response: feedsResponse} = envelope.get_feeds;

const feeds = map(feedsresponse.feed, feed => new Feed(feed));
const feeds = map(feedsResponse.feed, feed => createFeed(feed));

return response.setData(feeds);
});
}

/**
* Checks if any feed is currently syncing or if required feeds are not present.
*
* @returns {Promise<{isSyncing: boolean string}>} - A promise that resolves to an object indicating if any feed is syncing or if required feeds are not present or if there was an error.
* @throws {Error} - Throws an error if there is an issue fetching feed information.
*/

async checkFeedSync() {
try {
const response = await this.readFeedInformation();

const isFeedSyncing = response.data.some(
feed => feed.currentlySyncing || feed.syncNotAvailable,
);

const isNotPresent =
!response.data.some(feed => feed.feedType === NVT_FEED) ||
!response.data.some(feed => feed.feedType === SCAP_FEED);

return {
isSyncing: isFeedSyncing || isNotPresent,
};
} catch (error) {
console.error('Error checking if feed is syncing:', error);
throw error;
}
}
}

registerCommand('feedstatus', FeedStatus);
Expand Down
32 changes: 20 additions & 12 deletions src/gmp/commands/tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Task, {
HOSTS_ORDERING_SEQUENTIAL,
AUTO_DELETE_KEEP_DEFAULT_VALUE,
} from 'gmp/models/task';
import {FeedStatus} from './feedstatus';

import EntitiesCommand from './entities';
import EntityCommand from './entity';
Expand All @@ -24,21 +25,28 @@ export class TaskCommand extends EntityCommand {
super(http, 'task', Task);
}

start({id}) {
async start({id}) {
log.debug('Starting task...');

return this.httpPost({
cmd: 'start_task',
id,
})
.then(() => {
log.debug('Started task');
return this.get({id});
})
.catch(err => {
log.error('An error occurred while starting the task', id, err);
throw err;
try {
const feeds = new FeedStatus(this.http);

const status = await feeds.checkFeedSync();

if (status.isSyncing) {
throw new Error('Feed is currently syncing. Please try again later.');
}

await this.httpPost({
cmd: 'start_task',
id,
});

log.debug('Started task');
} catch (error) {
log.error('An error occurred while starting the task', id, error);
throw error;
}
}

stop({id}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* SPDX-FileCopyrightText: 2024 Greenbone AG
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import InfoPanel from 'web/components/panel/infopanel';
import BlankLink from 'web/components/link/blanklink';
import useTranslation from 'web/hooks/useTranslation';
import styled from 'styled-components';

import {
useFeedSyncStatus,
useFeedSyncDialog,
} from 'web/components/notification/FeedSyncNotification/helpers';

const NotificationWrapper = styled.div`
padding-bottom: 20px;
`;

const FeedSyncNotification = () => {
const [_] = useTranslation();
useFeedSyncStatus();

const [isFeedSyncDialogOpened, setIsFeedSyncDialogOpened, feedStatus] =
useFeedSyncDialog();

const handleCloseFeedSyncNotification = () => {
setIsFeedSyncDialogOpened(false);
};

if (!isFeedSyncDialogOpened) return null;

return (
<NotificationWrapper>
<InfoPanel
heading={
feedStatus.error
? _('Error fetching the feed')
: _('Feed is currently syncing.')
}
isWarning={Boolean(feedStatus.error)}
onCloseClick={handleCloseFeedSyncNotification}
>
{feedStatus.error ? (
<p>
{_(
`There was an error fetching the feed. It will be retried in a few minutes.`,
)}
</p>
) : (
<p>
{_(
`Please wait while the feed is syncing. Scans are not available during this time. For more information, visit the`,
)}{' '}
<BlankLink
to={
'https://docs.greenbone.net/GSM-Manual/gos-21.04/en/scanning.html?highlight=scan'
}
>
{_('Documentation')}.
</BlankLink>
</p>
)}
</InfoPanel>
</NotificationWrapper>
);
};

export default FeedSyncNotification;
Loading

0 comments on commit 49657d7

Please sign in to comment.