diff --git a/frontend/public/locales/en-GB/generalSettings.json b/frontend/public/locales/en-GB/generalSettings.json new file mode 100644 index 0000000000..151987397d --- /dev/null +++ b/frontend/public/locales/en-GB/generalSettings.json @@ -0,0 +1,21 @@ +{ + "total_retention_period": "Total Retention Period", + "move_to_s3": "Move to S3\n(should be lower than total retention period)", + "status_message": { + "success": "Your last call to change retention period to {{total_retention}} {{s3_part}} was successful.", + "failed": "Your last call to change retention period to {{total_retention}} {{s3_part}} failed. Please try again.", + "pending": "Your last call to change retention period to {{total_retention}} {{s3_part}} is pending. This may take some time.", + "s3_part": "and S3 to {{s3_retention}}" + }, + "retention_save_button": { + "pending": "Updating {{name}} retention period", + "success": "Save" + }, + "retention_request_race_condition": "Your request to change retention period has failed, as another request is still in process.", + "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", + "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", + "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", + "retention_confirmation": "Are you sure you want to change the retention period?", + "retention_confirmation_description": "This will change the amount of storage needed for saving {{name}}." +} diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json index 7ad8e9a716..682f034e69 100644 --- a/frontend/public/locales/en-GB/translation.json +++ b/frontend/public/locales/en-GB/translation.json @@ -13,16 +13,5 @@ "general": "General", "alert_channels": "Alert Channels", "all_errors": "All Exceptions" - }, - "settings": { - "total_retention_period": "Total Retention Period", - "move_to_s3": "Move to S3\n(should be lower than total retention period)", - "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", - "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", - "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", - "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", - "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", - "retention_confirmation": "Are you sure you want to change the retention period?", - "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." } } diff --git a/frontend/public/locales/en/generalSettings.json b/frontend/public/locales/en/generalSettings.json new file mode 100644 index 0000000000..151987397d --- /dev/null +++ b/frontend/public/locales/en/generalSettings.json @@ -0,0 +1,21 @@ +{ + "total_retention_period": "Total Retention Period", + "move_to_s3": "Move to S3\n(should be lower than total retention period)", + "status_message": { + "success": "Your last call to change retention period to {{total_retention}} {{s3_part}} was successful.", + "failed": "Your last call to change retention period to {{total_retention}} {{s3_part}} failed. Please try again.", + "pending": "Your last call to change retention period to {{total_retention}} {{s3_part}} is pending. This may take some time.", + "s3_part": "and S3 to {{s3_retention}}" + }, + "retention_save_button": { + "pending": "Updating {{name}} retention period", + "success": "Save" + }, + "retention_request_race_condition": "Your request to change retention period has failed, as another request is still in process.", + "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", + "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", + "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", + "retention_confirmation": "Are you sure you want to change the retention period?", + "retention_confirmation_description": "This will change the amount of storage needed for saving {{name}}." +} diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 7ad8e9a716..682f034e69 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -13,16 +13,5 @@ "general": "General", "alert_channels": "Alert Channels", "all_errors": "All Exceptions" - }, - "settings": { - "total_retention_period": "Total Retention Period", - "move_to_s3": "Move to S3\n(should be lower than total retention period)", - "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", - "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", - "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", - "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", - "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", - "retention_confirmation": "Are you sure you want to change the retention period?", - "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." } } diff --git a/frontend/src/api/settings/getRetention.ts b/frontend/src/api/settings/getRetention.ts index 4e5ad3d6f1..d19ab1a9d5 100644 --- a/frontend/src/api/settings/getRetention.ts +++ b/frontend/src/api/settings/getRetention.ts @@ -2,13 +2,15 @@ import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps } from 'types/api/settings/getRetention'; +import { PayloadProps, Props } from 'types/api/settings/getRetention'; -const getRetention = async (): Promise< - SuccessResponse | ErrorResponse -> => { +const getRetention = async ( + props: T, +): Promise> | ErrorResponse> => { try { - const response = await axios.get(`/settings/ttl`); + const response = await axios.get>( + `/settings/ttl?type=${props}`, + ); return { statusCode: 200, diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx index 10c9a1ac58..0f950ec05b 100644 --- a/frontend/src/container/GeneralSettings/GeneralSettings.tsx +++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx @@ -1,35 +1,67 @@ -import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +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 { PayloadProps as GetRetentionPayload } from 'types/api/settings/getRetention'; +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 { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; +import StatusMessage from './StatusMessage'; +import { ActionItemsContainer, ErrorText, ErrorTextContainer } from './styles'; type NumberOrNull = number | null; function GeneralSettings({ - ttlValuesPayload, + metricsTtlValuesPayload, + tracesTtlValuesPayload, getAvailableDiskPayload, + metricsTtlValuesRefetch, + tracesTtlValuesRefetch, }: GeneralSettingsProps): JSX.Element { - const { t } = useTranslation(); - const [modal, setModal] = useState(false); - const [postApiLoading, setPostApiLoading] = useState(false); - + 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 [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload); + const [metricsCurrentTTLValues, setMetricsCurrentTTLValues] = useState( + metricsTtlValuesPayload, + ); + const [tracesCurrentTTLValues, setTracesCurrentTTLValues] = useState( + tracesTtlValuesPayload, + ); + const { role } = useSelector((state) => state.app); const [setRetentionPermission] = useComponentPermission( @@ -55,195 +87,93 @@ function GeneralSettings({ ] = useState(null); useEffect(() => { - if (currentTTLValues) { - setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); - setMetricsS3RetentionPeriod( - currentTTLValues.metrics_move_ttl_duration_hrs - ? currentTTLValues.metrics_move_ttl_duration_hrs - : null, + if (metricsCurrentTTLValues) { + setMetricsTotalRetentionPeriod( + metricsCurrentTTLValues.metrics_ttl_duration_hrs, ); - setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); - setTracesS3RetentionPeriod( - currentTTLValues.traces_move_ttl_duration_hrs - ? currentTTLValues.traces_move_ttl_duration_hrs + setMetricsS3RetentionPeriod( + metricsCurrentTTLValues.metrics_move_ttl_duration_hrs + ? metricsCurrentTTLValues.metrics_move_ttl_duration_hrs : null, ); } - }, [currentTTLValues]); + }, [metricsCurrentTTLValues]); - const onModalToggleHandler = (): void => { - setModal((modal) => !modal); + 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(() => { - if (!setRetentionPermission) { - notification.error({ - message: `Sorry you don't have permission to make these changes`, - }); - return; - } - onModalToggleHandler(); - }, [setRetentionPermission]); + 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 renderConfig = [ - { - name: 'Metrics', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: metricsTotalRetentionPeriod, - setValue: setMetricsTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: metricsS3RetentionPeriod, - setValue: setMetricsS3RetentionPeriod, - hide: !s3Enabled, - }, - ], - }, - { - name: 'Traces', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: tracesTotalRetentionPeriod, - setValue: setTracesTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: tracesS3RetentionPeriod, - setValue: setTracesS3RetentionPeriod, - hide: !s3Enabled, - }, - ], - }, - ].map((category): JSX.Element | null => { - if ( - Array.isArray(category.retentionFields) && - category.retentionFields.length > 0 - ) { - return ( - - {category.name} - - {category.retentionFields.map((retentionField) => ( - - ))} - - ); - } - return null; - }); - - // eslint-disable-next-line sonarjs/cognitive-complexity - const onOkHandler = async (): Promise => { - try { - setPostApiLoading(true); - const apiCalls = []; - - if ( - !( - currentTTLValues?.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'metrics', - totalDuration: `${metricsTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${metricsS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - - if ( - !( - currentTTLValues?.traces_move_ttl_duration_hrs === - tracesS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'traces', - totalDuration: `${tracesTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${tracesS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - const apiCallSequence = ['metrics', 'traces']; - const apiResponses = await Promise.all(apiCalls.map((api) => api())); - - apiResponses.forEach((apiResponse, idx) => { - const name = apiCallSequence[idx]; - if (apiResponse) { - if (apiResponse.statusCode === 200) { - notification.success({ - message: 'Success!', - placement: 'topRight', - - description: t('settings.retention_success_message', { name }), - }); - } else { - notification.error({ - message: 'Error', - description: t('settings.retention_error_message', { name }), - placement: 'topRight', - }); - } - } - }); - onModalToggleHandler(); - setPostApiLoading(false); - } catch (error) { - notification.error({ - message: 'Error', - description: t('settings.retention_failed_message'), - placement: 'topRight', - }); - } - // Updates the currentTTL Values in order to avoid pushing the same values. - setCurrentTTLValues({ - metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, - metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, - traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, - traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, - }); - - setModal(false); - }; - - // eslint-disable-next-line sonarjs/cognitive-complexity - const [isDisabled, errorText] = useMemo((): [boolean, string] => { + 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('settings.retention_comparison_error', { name }), + t('retention_comparison_error', { name }), nullValueError: (name: string | number): string => - t('settings.retention_null_value_error', { name }), + t('retention_null_value_error', { name }), }; // Defaults to button not disabled and empty error message text. - let isDisabled = false; + let isMetricsSaveDisabled = false; + let isTracesSaveDisabled = false; let errorText = ''; if (s3Enabled) { @@ -251,19 +181,20 @@ function GeneralSettings({ (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) ) { - isDisabled = true; + isMetricsSaveDisabled = true; errorText = messages.compareError('metrics'); } else if ( (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) ) { - isDisabled = true; + isTracesSaveDisabled = true; errorText = messages.compareError('traces'); } } if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { - isDisabled = true; + isMetricsSaveDisabled = true; + isTracesSaveDisabled = true; if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { errorText = messages.nullValueError('metrics and traces'); } else if (!metricsTotalRetentionPeriod) { @@ -273,25 +204,240 @@ function GeneralSettings({ } } if ( - currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && - currentTTLValues.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && - currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod - ) { - isDisabled = true; - } - return [isDisabled, errorText]; + 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]; }, [ - currentTTLValues, + 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} @@ -306,34 +452,20 @@ function GeneralSettings({ {renderConfig} - - - {t('settings.retention_confirmation_description')} - - - - - ); } interface GeneralSettingsProps { - ttlValuesPayload: GetRetentionPayload; getAvailableDiskPayload: GetDisksPayload; + metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload; + tracesTtlValuesPayload: GetRetentionPeriodTracesPayload; + metricsTtlValuesRefetch: UseQueryResult< + ErrorResponse | SuccessResponse + >['refetch']; + tracesTtlValuesRefetch: UseQueryResult< + ErrorResponse | SuccessResponse + >['refetch']; } export default GeneralSettings; diff --git a/frontend/src/container/GeneralSettings/StatusMessage.tsx b/frontend/src/container/GeneralSettings/StatusMessage.tsx new file mode 100644 index 0000000000..bb62f34007 --- /dev/null +++ b/frontend/src/container/GeneralSettings/StatusMessage.tsx @@ -0,0 +1,69 @@ +import { green, orange, volcano } from '@ant-design/colors'; +import { InfoCircleOutlined } from '@ant-design/icons'; +import { Card, Col, Row } from 'antd'; +import React, { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { TStatus } from 'types/api/settings/getRetention'; + +import { convertHoursValueToRelevantUnitString } from './utils'; + +function StatusMessage({ + total_retention, + s3_retention, + status, +}: StatusMessageProps): JSX.Element | null { + const { t } = useTranslation(['generalSettings']); + + const messageColor = useMemo((): string => { + if (status === 'success') return green[6]; + if (status === 'pending') return orange[6]; + if (status === 'failed') return volcano[6]; + return 'inherit'; + }, [status]); + if (!status) { + return null; + } + const s3Part = + s3_retention && s3_retention !== -1 + ? t('status_message.s3_part', { + s3_retention: convertHoursValueToRelevantUnitString(s3_retention), + }) + : ''; + const statusMessage = + total_retention && total_retention !== -1 + ? t(`status_message.${status}`, { + total_retention: convertHoursValueToRelevantUnitString(total_retention), + s3_part: s3Part, + }) + : null; + + return statusMessage ? ( + + + + + + + + {statusMessage} + + + + ) : null; +} + +interface StatusMessageProps { + status: TStatus; + total_retention: number | undefined; + s3_retention: number | undefined; +} +export default StatusMessage; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 633c4822e6..2c66042500 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -5,15 +5,33 @@ import Spinner from 'components/Spinner'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { useQueries } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { TTTLType } from 'types/api/settings/common'; +import { PayloadProps as GetRetentionPeriodAPIPayloadProps } from 'types/api/settings/getRetention'; import GeneralSettingsContainer from './GeneralSettings'; +type TRetentionAPIReturn = Promise< + SuccessResponse> | ErrorResponse +>; + function GeneralSettings(): JSX.Element { const { t } = useTranslation('common'); - const [getRetentionPeriodApiResponse, getDisksResponse] = useQueries([ + + const [ + getRetentionPeriodMetricsApiResponse, + getRetentionPeriodTracesApiResponse, + getDisksResponse, + ] = useQueries([ { - queryFn: getRetentionPeriodApi, - queryKey: 'getRetentionPeriodApi', + queryFn: (): TRetentionAPIReturn<'metrics'> => + getRetentionPeriodApi('metrics'), + queryKey: 'getRetentionPeriodApiMetrics', + }, + { + queryFn: (): TRetentionAPIReturn<'traces'> => + getRetentionPeriodApi('traces'), + queryKey: 'getRetentionPeriodApiTraces', }, { queryFn: getDisks, @@ -21,21 +39,38 @@ function GeneralSettings(): JSX.Element { }, ]); - if (getRetentionPeriodApiResponse.isError || getDisksResponse.isError) { + // Error State - When RetentionPeriodMetricsApi or getDiskApi gets errored out. + if (getRetentionPeriodMetricsApiResponse.isError || getDisksResponse.isError) { return ( - {getRetentionPeriodApiResponse.data?.error || + {getRetentionPeriodMetricsApiResponse.data?.error || getDisksResponse.data?.error || t('something_went_wrong')} ); } + // Error State - When RetentionPeriodTracesApi or getDiskApi gets errored out. + if (getRetentionPeriodTracesApiResponse.isError || getDisksResponse.isError) { + return ( + + {getRetentionPeriodTracesApiResponse.data?.error || + getDisksResponse.data?.error || + t('something_went_wrong')} + + ); + } + + // Loading State - When Metrics, Traces and Disk API are in progress and the promise has not been resolved/reject. if ( - getRetentionPeriodApiResponse.isLoading || + getRetentionPeriodMetricsApiResponse.isLoading || getDisksResponse.isLoading || !getDisksResponse.data?.payload || - !getRetentionPeriodApiResponse.data?.payload + !getRetentionPeriodMetricsApiResponse.data?.payload || + getRetentionPeriodTracesApiResponse.isLoading || + getDisksResponse.isLoading || + !getDisksResponse.data?.payload || + !getRetentionPeriodTracesApiResponse.data?.payload ) { return ; } @@ -44,7 +79,10 @@ function GeneralSettings(): JSX.Element { ); diff --git a/frontend/src/container/GeneralSettings/styles.ts b/frontend/src/container/GeneralSettings/styles.ts index 6dc7b9fe71..9c9e7bb312 100644 --- a/frontend/src/container/GeneralSettings/styles.ts +++ b/frontend/src/container/GeneralSettings/styles.ts @@ -90,3 +90,11 @@ export const RetentionFieldLabel = styled(TypographyComponent)` export const RetentionFieldInputContainer = styled.div` display: inline-flex; `; + +export const ActionItemsContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + margin-top: 10%; +`; diff --git a/frontend/src/container/GeneralSettings/utils.ts b/frontend/src/container/GeneralSettings/utils.ts index 30efd6786a..639f0a4930 100644 --- a/frontend/src/container/GeneralSettings/utils.ts +++ b/frontend/src/container/GeneralSettings/utils.ts @@ -23,9 +23,13 @@ export const TimeUnits: ITimeUnit[] = [ }, ]; +interface ITimeUnitConversion { + value: number; + timeUnitValue: SettingPeriod; +} export const convertHoursValueToRelevantUnit = ( value: number, -): { value: number; timeUnitValue: SettingPeriod } => { +): ITimeUnitConversion => { if (value) for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) { const timeUnit = TimeUnits[idx]; @@ -40,3 +44,13 @@ export const convertHoursValueToRelevantUnit = ( } return { value, timeUnitValue: TimeUnits[0].value }; }; + +export const convertHoursValueToRelevantUnitString = ( + value: number, +): string => { + if (!value) return ''; + const convertedTimeUnit = convertHoursValueToRelevantUnit(value); + return `${convertedTimeUnit.value} ${convertedTimeUnit.timeUnitValue}${ + convertedTimeUnit.value >= 2 ? 's' : '' + }`; +}; diff --git a/frontend/src/types/api/settings/common.ts b/frontend/src/types/api/settings/common.ts new file mode 100644 index 0000000000..67c0c3ed03 --- /dev/null +++ b/frontend/src/types/api/settings/common.ts @@ -0,0 +1 @@ +export type TTTLType = 'metrics' | 'traces'; diff --git a/frontend/src/types/api/settings/getRetention.ts b/frontend/src/types/api/settings/getRetention.ts index f8a2f8b1bc..5f547ffd6c 100644 --- a/frontend/src/types/api/settings/getRetention.ts +++ b/frontend/src/types/api/settings/getRetention.ts @@ -1,6 +1,25 @@ -export interface PayloadProps { +import { TTTLType } from './common'; + +export type TStatus = '' | 'pending' | 'failed' | 'success'; +export interface PayloadPropsMetrics { metrics_ttl_duration_hrs: number; metrics_move_ttl_duration_hrs?: number; + status: TStatus; + expected_metrics_move_ttl_duration_hrs?: number; + expected_metrics_ttl_duration_hrs?: number; +} +export interface PayloadPropsTraces { traces_ttl_duration_hrs: number; traces_move_ttl_duration_hrs?: number; + status: TStatus; + expected_traces_move_ttl_duration_hrs?: number; + expected_traces_ttl_duration_hrs?: number; } + +export type Props = TTTLType; + +export type PayloadProps = T extends 'metrics' + ? PayloadPropsMetrics + : T extends 'traces' + ? PayloadPropsTraces + : never; diff --git a/frontend/src/types/api/settings/setRetention.ts b/frontend/src/types/api/settings/setRetention.ts index 134e409f18..29cb20481b 100644 --- a/frontend/src/types/api/settings/setRetention.ts +++ b/frontend/src/types/api/settings/setRetention.ts @@ -1,5 +1,7 @@ +import { TTTLType } from './common'; + export interface Props { - type: 'metrics' | 'traces'; + type: TTTLType; totalDuration: string; coldStorage?: 's3' | null; toColdDuration?: string; diff --git a/frontend/src/types/common/index.ts b/frontend/src/types/common/index.ts index a55f38c1af..c32bc6f44b 100644 --- a/frontend/src/types/common/index.ts +++ b/frontend/src/types/common/index.ts @@ -10,6 +10,8 @@ export type Unauthorized = 401; export type NotFound = 404; +export type Conflict = 409; + export type ServerError = 500; export type SuccessStatusCode = Created | Success; @@ -20,6 +22,7 @@ export type ErrorStatusCode = | Unauthorized | NotFound | ServerError - | BadRequest; + | BadRequest + | Conflict; export type StatusCode = SuccessStatusCode | ErrorStatusCode;