diff --git a/graylog2-web-interface/src/@types/graylog-web-plugin/index.d.ts b/graylog2-web-interface/src/@types/graylog-web-plugin/index.d.ts index e11dc09baa75..47df5f43396e 100644 --- a/graylog2-web-interface/src/@types/graylog-web-plugin/index.d.ts +++ b/graylog2-web-interface/src/@types/graylog-web-plugin/index.d.ts @@ -191,12 +191,31 @@ interface PluginDataWarehouse { StreamIlluminateProcessingSection: React.ComponentType<{ stream: Stream, }>, - StreamIndexSetDataWarehouseWarning: React.ComponentType<{streamId: string, isArchivingEnabled: boolean}>, + DataWarehouseJobs: React.ComponentType<{}>, + StreamIndexSetDataWarehouseWarning: React.ComponentType<{streamId: string, isArchivingEnabled: boolean}>, + fetchStreamDataWarehouseStatus: (streamId: string) => Promise<{ + id: string, + archive_name: string, + enabled: boolean, + stream_id: string, + retention_time: number, + }>, + fetchStreamDataWarehouse: (streamId: string) => Promise<{ + id: string, + archive_config_id: string, + message_count: number, + archive_name: string, + timestamp_from: string, + timestamp_to: string, + restore_history: Array<{id:string}>, + + }>; getStreamDataWarehouseTableElements: (permission: Immutable.List) => { attributeName: string, attributes: Array<{ id: string, title: string }>, columnRenderer: ColumnRenderers, } | undefined, + DataWarehouseStreamDeleteWarning: React.ComponentType<{}>, } declare module 'graylog-web-plugin/plugin' { diff --git a/graylog2-web-interface/src/components/streams/StreamsOverview/StreamActions.tsx b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamActions.tsx index 0c28eb3361c2..583b76a19ed4 100644 --- a/graylog2-web-interface/src/components/streams/StreamsOverview/StreamActions.tsx +++ b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamActions.tsx @@ -22,9 +22,7 @@ import { Button, ButtonToolbar, MenuItem } from 'components/bootstrap'; import type { Stream, StreamRule } from 'stores/streams/StreamsStore'; import StreamsStore from 'stores/streams/StreamsStore'; import Routes from 'routing/Routes'; -import HideOnCloud from 'util/conditional/HideOnCloud'; import { StartpageStore } from 'stores/users/StartpageStore'; -import UserNotification from 'util/UserNotification'; import StreamRuleModal from 'components/streamrules/StreamRuleModal'; import EntityShareModal from 'components/permissions/EntityShareModal'; import { StreamRulesStore } from 'stores/streams/StreamRulesStore'; @@ -35,6 +33,10 @@ import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants'; import useSelectedEntities from 'components/common/EntityDataTable/hooks/useSelectedEntities'; import { MoreActions } from 'components/common/EntityDataTable'; import { LinkContainer } from 'components/common/router'; +import HideOnCloud from 'util/conditional/HideOnCloud'; +import UserNotification from 'util/UserNotification'; + +import StreamDeleteModal from './StreamDeleteModal'; import StreamModal from '../StreamModal'; @@ -53,6 +55,7 @@ const StreamActions = ({ }) => { const currentUser = useCurrentUser(); const { deselectEntity } = useSelectedEntities(); + const [showDeleteModal, setDeleteModal] = useState(false); const [showEntityShareModal, setShowEntityShareModal] = useState(false); const [showStreamRuleModal, setShowStreamRuleModal] = useState(false); const [showUpdateModal, setShowUpdateModal] = useState(false); @@ -94,6 +97,10 @@ const StreamActions = ({ setShowEntityShareModal((cur) => !cur); }, [sendTelemetry]); + const toggleDeleteModal = useCallback(() => { + setDeleteModal((cur) => !cur); + }, []); + const toggleUpdateModal = useCallback(() => { setShowUpdateModal((cur) => !cur); }, []); @@ -107,21 +114,19 @@ const StreamActions = ({ }, []); const onDelete = useCallback(() => { - // eslint-disable-next-line no-alert - if (window.confirm('Do you really want to remove this stream?')) { - sendTelemetry(TELEMETRY_EVENT_TYPE.STREAMS.STREAM_ITEM_DELETED, { - app_pathname: 'streams', - app_action_value: 'stream-item-delete', - }); - - StreamsStore.remove(stream.id).then(() => { - deselectEntity(stream.id); - UserNotification.success(`Stream '${stream.title}' was deleted successfully.`, 'Success'); - }).catch((error) => { - UserNotification.error(`An error occurred while deleting the stream. ${error}`); - }); - } - }, [deselectEntity, sendTelemetry, stream.id, stream.title]); + sendTelemetry(TELEMETRY_EVENT_TYPE.STREAMS.STREAM_ITEM_DELETED, { + app_pathname: 'streams', + app_action_value: 'stream-item-delete', + }); + + StreamsStore.remove(stream.id).then(() => { + deselectEntity(stream.id); + UserNotification.success(`Stream '${stream.title}' was deleted successfully.`, 'Success'); + toggleDeleteModal(); + }).catch((error) => { + UserNotification.error(`An error occurred while deleting the stream. ${error}`); + }); + }, [deselectEntity, sendTelemetry, stream.id, stream.title, toggleDeleteModal]); const onSaveStreamRule = useCallback((_streamRuleId: string, streamRule: StreamRule) => StreamRulesStore.create(stream.id, streamRule, () => { sendTelemetry(TELEMETRY_EVENT_TYPE.STREAMS.STREAM_ITEM_RULE_SAVED, { @@ -221,7 +226,7 @@ const StreamActions = ({ - + Delete this stream {isDefaultStream && } @@ -257,6 +262,12 @@ const StreamActions = ({ description="Search for a User or Team to add as collaborator on this stream." onClose={toggleEntityShareModal} /> )} + {showDeleteModal && ( + + )} ); }; diff --git a/graylog2-web-interface/src/components/streams/StreamsOverview/StreamDeleteModal.tsx b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamDeleteModal.tsx new file mode 100644 index 000000000000..79f1488d3057 --- /dev/null +++ b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamDeleteModal.tsx @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import * as React from 'react'; +import { useMemo } from 'react'; + +import usePluginEntities from 'hooks/usePluginEntities'; +import { ConfirmDialog } from 'components/common'; +import useStreamDataWarehouseHasData from 'components/streams/StreamsOverview/hooks/useStreamDataWarehouseHasData'; +import useIsStreamDataWarehouseEnabled from 'components/streams/StreamsOverview/hooks/useIsStreamDataWarehouseEnabled'; + +type Props = { + onDelete: () => void, + streamId: string, + streamTitle: string, + onCancel: () => void, +}; + +const StreamDeleteModal = ({ onDelete, streamId, streamTitle, onCancel }: Props) => { + const DataWarehouseStreamDeleteWarning = usePluginEntities('dataWarehouse')?.[0]?.DataWarehouseStreamDeleteWarning; + const streamDataWarehouseHasData = useStreamDataWarehouseHasData(streamId, !!DataWarehouseStreamDeleteWarning); + const isDataWarehouseEnable = useIsStreamDataWarehouseEnabled(streamId, !!DataWarehouseStreamDeleteWarning); + + const shouldShowWarning = useMemo(() => isDataWarehouseEnable || streamDataWarehouseHasData, [isDataWarehouseEnable, streamDataWarehouseHasData]); + + return ( + + {shouldShowWarning ? : `Do you really want to remove stream: ${streamTitle}?`} + + ); +}; + +export default StreamDeleteModal; diff --git a/graylog2-web-interface/src/components/streams/StreamsOverview/StreamsOverview.tsx b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamsOverview.tsx index a932d1f40005..6146a5dd75e0 100644 --- a/graylog2-web-interface/src/components/streams/StreamsOverview/StreamsOverview.tsx +++ b/graylog2-web-interface/src/components/streams/StreamsOverview/StreamsOverview.tsx @@ -70,7 +70,7 @@ const StreamsOverview = ({ indexSets }: Props) => { tableLayout={defaultLayout} fetchEntities={fetchStreams} keyFn={keyFn} - actionsCellWidth={180} + actionsCellWidth={200} expandedSectionsRenderer={expandedSections} bulkSelection={{ actions: bulkActions }} entityAttributesAreCamelCase={false} diff --git a/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useIsStreamDataWarehouseEnabled.ts b/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useIsStreamDataWarehouseEnabled.ts new file mode 100644 index 000000000000..dab63ad418e8 --- /dev/null +++ b/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useIsStreamDataWarehouseEnabled.ts @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import { useQuery } from '@tanstack/react-query'; + +import usePluginEntities from 'hooks/usePluginEntities'; + +const useIsStreamDataWarehouseEnabled = (streamId: string, enabled: boolean) => { + const { fetchStreamDataWarehouseStatus } = usePluginEntities('dataWarehouse')[0] ?? {}; + const { data: status, isError, isLoading } = useQuery(['data-warehouse-config', streamId, 'enabled'], + () => fetchStreamDataWarehouseStatus(streamId), + { enabled: fetchStreamDataWarehouseStatus && enabled }, + ); + + return (isLoading || isError) ? undefined : status?.enabled; +}; + +export default useIsStreamDataWarehouseEnabled; diff --git a/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useStreamDataWarehouseHasData.ts b/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useStreamDataWarehouseHasData.ts new file mode 100644 index 000000000000..97c8a9203355 --- /dev/null +++ b/graylog2-web-interface/src/components/streams/StreamsOverview/hooks/useStreamDataWarehouseHasData.ts @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 Graylog, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + */ +import { useQuery } from '@tanstack/react-query'; + +import usePluginEntities from 'hooks/usePluginEntities'; + +const useStreamDataWarehouseHasData = (streamId: string, enabled: boolean) => { + const { fetchStreamDataWarehouse } = usePluginEntities('dataWarehouse')[0] ?? {}; + const { data: dataWarehouse, isError, isLoading } = useQuery(['stream', 'data-warehouse', streamId], + () => fetchStreamDataWarehouse(streamId), + { enabled: fetchStreamDataWarehouse && enabled }, + ); + + return (isLoading || isError) ? undefined : ( + dataWarehouse?.message_count > 1 || dataWarehouse?.restore_history?.length > 0 + ); +}; + +export default useStreamDataWarehouseHasData;