import { LoadingOutlined } from '@ant-design/icons'; import { Button, Col, Divider, Modal, notification, Row, Spin, Typography, } from 'antd'; import setRetentionApi from 'api/settings/setRetention'; import TextToolTip from 'components/TextToolTip'; import useComponentPermission from 'hooks/useComponentPermission'; import find from 'lodash-es/find'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; import { useInterval } from 'react-use'; import { AppState } from 'store/reducers'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { IDiskType, PayloadProps as GetDisksPayload, } from 'types/api/disks/getDisks'; import { TTTLType } from 'types/api/settings/common'; import { PayloadPropsMetrics as GetRetentionPeriodMetricsPayload, PayloadPropsTraces as GetRetentionPeriodTracesPayload, } from 'types/api/settings/getRetention'; import AppReducer from 'types/reducer/app'; import Retention from './Retention'; import StatusMessage from './StatusMessage'; import { ActionItemsContainer, ErrorText, ErrorTextContainer } from './styles'; type NumberOrNull = number | null; function GeneralSettings({ metricsTtlValuesPayload, tracesTtlValuesPayload, getAvailableDiskPayload, metricsTtlValuesRefetch, tracesTtlValuesRefetch, }: GeneralSettingsProps): JSX.Element { const { t } = useTranslation(['generalSettings']); const [modalMetrics, setModalMetrics] = useState(false); const [postApiLoadingMetrics, setPostApiLoadingMetrics] = useState( false, ); const [postApiLoadingTraces, setPostApiLoadingTraces] = useState( false, ); const [modalTraces, setModalTraces] = useState(false); const [availableDisks] = useState(getAvailableDiskPayload); const [metricsCurrentTTLValues, setMetricsCurrentTTLValues] = useState( metricsTtlValuesPayload, ); const [tracesCurrentTTLValues, setTracesCurrentTTLValues] = useState( tracesTtlValuesPayload, ); const { role } = useSelector((state) => state.app); const [setRetentionPermission] = useComponentPermission( ['set_retention_period'], role, ); const [ metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod, ] = useState(null); const [ metricsS3RetentionPeriod, setMetricsS3RetentionPeriod, ] = useState(null); const [ tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod, ] = useState(null); const [ tracesS3RetentionPeriod, setTracesS3RetentionPeriod, ] = useState(null); useEffect(() => { if (metricsCurrentTTLValues) { setMetricsTotalRetentionPeriod( metricsCurrentTTLValues.metrics_ttl_duration_hrs, ); setMetricsS3RetentionPeriod( metricsCurrentTTLValues.metrics_move_ttl_duration_hrs ? metricsCurrentTTLValues.metrics_move_ttl_duration_hrs : null, ); } }, [metricsCurrentTTLValues]); useEffect(() => { if (tracesCurrentTTLValues) { setTracesTotalRetentionPeriod( tracesCurrentTTLValues.traces_ttl_duration_hrs, ); setTracesS3RetentionPeriod( tracesCurrentTTLValues.traces_move_ttl_duration_hrs ? tracesCurrentTTLValues.traces_move_ttl_duration_hrs : null, ); } }, [tracesCurrentTTLValues]); useInterval( async (): Promise => { if (metricsTtlValuesPayload.status === 'pending') { metricsTtlValuesRefetch(); } }, metricsTtlValuesPayload.status === 'pending' ? 1000 : null, ); useInterval( async (): Promise => { if (tracesTtlValuesPayload.status === 'pending') { tracesTtlValuesRefetch(); } }, tracesTtlValuesPayload.status === 'pending' ? 1000 : null, ); const onModalToggleHandler = (type: TTTLType): void => { if (type === 'metrics') setModalMetrics((modal) => !modal); if (type === 'traces') setModalTraces((modal) => !modal); }; const onPostApiLoadingHandler = (type: TTTLType): void => { if (type === 'metrics') setPostApiLoadingMetrics((modal) => !modal); if (type === 'traces') setPostApiLoadingTraces((modal) => !modal); }; const onClickSaveHandler = useCallback( (type: TTTLType) => { if (!setRetentionPermission) { notification.error({ message: `Sorry you don't have permission to make these changes`, }); return; } onModalToggleHandler(type); }, [setRetentionPermission], ); const s3Enabled = useMemo( () => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'), [availableDisks], ); const [isMetricsSaveDisabled, isTracesSaveDisabled, errorText] = useMemo((): [ boolean, boolean, string, // eslint-disable-next-line sonarjs/cognitive-complexity ] => { // Various methods to return dynamic error message text. const messages = { compareError: (name: string | number): string => t('retention_comparison_error', { name }), nullValueError: (name: string | number): string => t('retention_null_value_error', { name }), }; // Defaults to button not disabled and empty error message text. let isMetricsSaveDisabled = false; let isTracesSaveDisabled = false; let errorText = ''; if (s3Enabled) { if ( (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) ) { isMetricsSaveDisabled = true; errorText = messages.compareError('metrics'); } else if ( (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) ) { isTracesSaveDisabled = true; errorText = messages.compareError('traces'); } } if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { isMetricsSaveDisabled = true; isTracesSaveDisabled = true; if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { errorText = messages.nullValueError('metrics and traces'); } else if (!metricsTotalRetentionPeriod) { errorText = messages.nullValueError('metrics'); } else if (!tracesTotalRetentionPeriod) { errorText = messages.nullValueError('traces'); } } if ( metricsCurrentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && metricsCurrentTTLValues.metrics_move_ttl_duration_hrs === metricsS3RetentionPeriod ) isMetricsSaveDisabled = true; if ( tracesCurrentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && tracesCurrentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod ) isTracesSaveDisabled = true; return [isMetricsSaveDisabled, isTracesSaveDisabled, errorText]; }, [ metricsCurrentTTLValues.metrics_move_ttl_duration_hrs, metricsCurrentTTLValues?.metrics_ttl_duration_hrs, metricsS3RetentionPeriod, metricsTotalRetentionPeriod, s3Enabled, t, tracesCurrentTTLValues.traces_move_ttl_duration_hrs, tracesCurrentTTLValues.traces_ttl_duration_hrs, tracesS3RetentionPeriod, tracesTotalRetentionPeriod, ]); // eslint-disable-next-line sonarjs/cognitive-complexity const onOkHandler = async (type: TTTLType): Promise => { try { onPostApiLoadingHandler(type); const setTTLResponse = await setRetentionApi({ type, totalDuration: `${ (type === 'metrics' ? metricsTotalRetentionPeriod : tracesTotalRetentionPeriod) || -1 }h`, coldStorage: s3Enabled ? 's3' : null, toColdDuration: `${ (type === 'metrics' ? metricsS3RetentionPeriod : tracesS3RetentionPeriod) || -1 }h`, }); let hasSetTTLFailed = false; if (setTTLResponse.statusCode === 409) { hasSetTTLFailed = true; notification.error({ message: 'Error', description: t('retention_request_race_condition'), placement: 'topRight', }); } if (type === 'metrics') { metricsTtlValuesRefetch(); if (!hasSetTTLFailed) // Updates the currentTTL Values in order to avoid pushing the same values. setMetricsCurrentTTLValues({ metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, status: '', }); } else if (type === 'traces') { tracesTtlValuesRefetch(); if (!hasSetTTLFailed) // Updates the currentTTL Values in order to avoid pushing the same values. setTracesCurrentTTLValues({ traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, status: '', }); } } catch (error) { notification.error({ message: 'Error', description: t('retention_failed_message'), placement: 'topRight', }); } onPostApiLoadingHandler(type); onModalToggleHandler(type); }; const renderConfig = [ { name: 'Metrics', retentionFields: [ { name: t('total_retention_period'), value: metricsTotalRetentionPeriod, setValue: setMetricsTotalRetentionPeriod, }, { name: t('move_to_s3'), value: metricsS3RetentionPeriod, setValue: setMetricsS3RetentionPeriod, hide: !s3Enabled, }, ], save: { modal: modalMetrics, modalOpen: (): void => onClickSaveHandler('metrics'), apiLoading: postApiLoadingMetrics, saveButtonText: metricsTtlValuesPayload.status === 'pending' ? ( } />{' '} {t('retention_save_button.pending', { name: 'metrics' })} ) : ( {t('retention_save_button.success')} ), isDisabled: metricsTtlValuesPayload.status === 'pending' || isMetricsSaveDisabled, }, statusComponent: ( ), }, { name: 'Traces', retentionFields: [ { name: t('total_retention_period'), value: tracesTotalRetentionPeriod, setValue: setTracesTotalRetentionPeriod, }, { name: t('move_to_s3'), value: tracesS3RetentionPeriod, setValue: setTracesS3RetentionPeriod, hide: !s3Enabled, }, ], save: { modal: modalTraces, modalOpen: (): void => onClickSaveHandler('traces'), apiLoading: postApiLoadingTraces, saveButtonText: tracesTtlValuesPayload.status === 'pending' ? ( } />{' '} {t('retention_save_button.pending', { name: 'traces' })} ) : ( {t('retention_save_button.success')} ), isDisabled: tracesTtlValuesPayload.status === 'pending' || isTracesSaveDisabled, }, statusComponent: ( ), }, ].map((category, idx, renderArr): JSX.Element | null => { if ( Array.isArray(category.retentionFields) && category.retentionFields.length > 0 ) { return ( {category.name} {category.retentionFields.map((retentionField) => ( ))} {category.statusComponent} onModalToggleHandler(category.name.toLowerCase() as TTTLType) } onOk={(): Promise => onOkHandler(category.name.toLowerCase() as TTTLType) } centered visible={category.save.modal} confirmLoading={category.save.apiLoading} > {t('retention_confirmation_description', { name: category.name.toLowerCase(), })} {idx < renderArr.length && ( )} ); } return null; }); return ( {Element} {errorText && {errorText}} {renderConfig} ); } interface GeneralSettingsProps { getAvailableDiskPayload: GetDisksPayload; metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload; tracesTtlValuesPayload: GetRetentionPeriodTracesPayload; metricsTtlValuesRefetch: UseQueryResult< ErrorResponse | SuccessResponse >['refetch']; tracesTtlValuesRefetch: UseQueryResult< ErrorResponse | SuccessResponse >['refetch']; } export default GeneralSettings;