mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 07:59:00 +08:00
chore: TTL and S3 config related changes (#1201)
* fix: 🐛 convert TTL APIs to async * chore: add archive support * chore: update TTL async APIs according to new design * chore: 🔥 clean removeTTL API * fix: metrics s3 config * feat: ttl async with polling (#1195) * feat: ttl state message change and time unit language changes (#1197) * test: ✅ update tests for async TTL api * feat: ttl message info icon (#1202) * feat: ttl pr review changes * chore: refractoring Co-authored-by: makeavish <makeavish786@gmail.com> Co-authored-by: Pranshu Chittora <pranshu@signoz.io> Co-authored-by: palash-signoz <palash@signoz.io> Co-authored-by: Pranay Prateek <pranay@signoz.io>
This commit is contained in:
parent
f92e4798ce
commit
642c6c5920
21
frontend/public/locales/en-GB/generalSettings.json
Normal file
21
frontend/public/locales/en-GB/generalSettings.json
Normal file
@ -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}}."
|
||||
}
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
21
frontend/public/locales/en/generalSettings.json
Normal file
21
frontend/public/locales/en/generalSettings.json
Normal file
@ -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}}."
|
||||
}
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
@ -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<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
const getRetention = async <T extends Props>(
|
||||
props: T,
|
||||
): Promise<SuccessResponse<PayloadProps<T>> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/settings/ttl`);
|
||||
const response = await axios.get<PayloadProps<T>>(
|
||||
`/settings/ttl?type=${props}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
@ -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<boolean>(false);
|
||||
const [postApiLoading, setPostApiLoading] = useState<boolean>(false);
|
||||
|
||||
const { t } = useTranslation(['generalSettings']);
|
||||
const [modalMetrics, setModalMetrics] = useState<boolean>(false);
|
||||
const [postApiLoadingMetrics, setPostApiLoadingMetrics] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [postApiLoadingTraces, setPostApiLoadingTraces] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const [modalTraces, setModalTraces] = useState<boolean>(false);
|
||||
const [availableDisks] = useState<IDiskType[]>(getAvailableDiskPayload);
|
||||
|
||||
const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload);
|
||||
const [metricsCurrentTTLValues, setMetricsCurrentTTLValues] = useState(
|
||||
metricsTtlValuesPayload,
|
||||
);
|
||||
const [tracesCurrentTTLValues, setTracesCurrentTTLValues] = useState(
|
||||
tracesTtlValuesPayload,
|
||||
);
|
||||
|
||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const [setRetentionPermission] = useComponentPermission(
|
||||
@ -55,195 +87,93 @@ function GeneralSettings({
|
||||
] = useState<NumberOrNull>(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<void> => {
|
||||
if (metricsTtlValuesPayload.status === 'pending') {
|
||||
metricsTtlValuesRefetch();
|
||||
}
|
||||
},
|
||||
metricsTtlValuesPayload.status === 'pending' ? 1000 : null,
|
||||
);
|
||||
|
||||
useInterval(
|
||||
async (): Promise<void> => {
|
||||
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 (
|
||||
<Col flex="40%" style={{ minWidth: 475 }} key={category.name}>
|
||||
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||
|
||||
{category.retentionFields.map((retentionField) => (
|
||||
<Retention
|
||||
key={retentionField.name}
|
||||
text={retentionField.name}
|
||||
retentionValue={retentionField.value}
|
||||
setRetentionValue={retentionField.setValue}
|
||||
hide={!!retentionField.hide}
|
||||
/>
|
||||
))}
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const onOkHandler = async (): Promise<void> => {
|
||||
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<void> => {
|
||||
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' ? (
|
||||
<span>
|
||||
<Spin spinning size="small" indicator={<LoadingOutlined spin />} />{' '}
|
||||
{t('retention_save_button.pending', { name: 'metrics' })}
|
||||
</span>
|
||||
) : (
|
||||
<span>{t('retention_save_button.success')}</span>
|
||||
),
|
||||
isDisabled:
|
||||
metricsTtlValuesPayload.status === 'pending' || isMetricsSaveDisabled,
|
||||
},
|
||||
statusComponent: (
|
||||
<StatusMessage
|
||||
total_retention={metricsTtlValuesPayload.expected_metrics_ttl_duration_hrs}
|
||||
status={metricsTtlValuesPayload.status}
|
||||
s3_retention={
|
||||
metricsTtlValuesPayload.expected_metrics_move_ttl_duration_hrs
|
||||
}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
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' ? (
|
||||
<span>
|
||||
<Spin spinning size="small" indicator={<LoadingOutlined spin />} />{' '}
|
||||
{t('retention_save_button.pending', { name: 'traces' })}
|
||||
</span>
|
||||
) : (
|
||||
<span>{t('retention_save_button.success')}</span>
|
||||
),
|
||||
isDisabled:
|
||||
tracesTtlValuesPayload.status === 'pending' || isTracesSaveDisabled,
|
||||
},
|
||||
statusComponent: (
|
||||
<StatusMessage
|
||||
total_retention={tracesTtlValuesPayload.expected_traces_ttl_duration_hrs}
|
||||
status={tracesTtlValuesPayload.status}
|
||||
s3_retention={tracesTtlValuesPayload.expected_traces_move_ttl_duration_hrs}
|
||||
/>
|
||||
),
|
||||
},
|
||||
].map((category, idx, renderArr): JSX.Element | null => {
|
||||
if (
|
||||
Array.isArray(category.retentionFields) &&
|
||||
category.retentionFields.length > 0
|
||||
) {
|
||||
return (
|
||||
<React.Fragment key={category.name}>
|
||||
<Col xs={22} xl={11} key={category.name}>
|
||||
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||
|
||||
{category.retentionFields.map((retentionField) => (
|
||||
<Retention
|
||||
key={retentionField.name}
|
||||
text={retentionField.name}
|
||||
retentionValue={retentionField.value}
|
||||
setRetentionValue={retentionField.setValue}
|
||||
hide={!!retentionField.hide}
|
||||
/>
|
||||
))}
|
||||
<ActionItemsContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={category.save.modalOpen}
|
||||
disabled={category.save.isDisabled}
|
||||
>
|
||||
{category.save.saveButtonText}
|
||||
</Button>
|
||||
{category.statusComponent}
|
||||
</ActionItemsContainer>
|
||||
<Modal
|
||||
title={t('retention_confirmation')}
|
||||
focusTriggerAfterClose
|
||||
forceRender
|
||||
destroyOnClose
|
||||
closable
|
||||
onCancel={(): void =>
|
||||
onModalToggleHandler(category.name.toLowerCase() as TTTLType)
|
||||
}
|
||||
onOk={(): Promise<void> =>
|
||||
onOkHandler(category.name.toLowerCase() as TTTLType)
|
||||
}
|
||||
centered
|
||||
visible={category.save.modal}
|
||||
confirmLoading={category.save.apiLoading}
|
||||
>
|
||||
<Typography>
|
||||
{t('retention_confirmation_description', {
|
||||
name: category.name.toLowerCase(),
|
||||
})}
|
||||
</Typography>
|
||||
</Modal>
|
||||
</Col>
|
||||
{idx < renderArr.length && (
|
||||
<Col xs={0} xl={1} style={{ textAlign: 'center' }}>
|
||||
<Divider type="vertical" dashed style={{ height: '100%' }} />
|
||||
</Col>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
return (
|
||||
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||
{Element}
|
||||
@ -306,34 +452,20 @@ function GeneralSettings({
|
||||
</ErrorTextContainer>
|
||||
|
||||
<Row justify="space-around">{renderConfig}</Row>
|
||||
|
||||
<Modal
|
||||
title={t('settings.retention_confirmation')}
|
||||
focusTriggerAfterClose
|
||||
forceRender
|
||||
destroyOnClose
|
||||
closable
|
||||
onCancel={onModalToggleHandler}
|
||||
onOk={onOkHandler}
|
||||
centered
|
||||
visible={modal}
|
||||
confirmLoading={postApiLoading}
|
||||
>
|
||||
<Typography>{t('settings.retention_confirmation_description')}</Typography>
|
||||
</Modal>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button onClick={onClickSaveHandler} disabled={isDisabled} type="primary">
|
||||
Save
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
interface GeneralSettingsProps {
|
||||
ttlValuesPayload: GetRetentionPayload;
|
||||
getAvailableDiskPayload: GetDisksPayload;
|
||||
metricsTtlValuesPayload: GetRetentionPeriodMetricsPayload;
|
||||
tracesTtlValuesPayload: GetRetentionPeriodTracesPayload;
|
||||
metricsTtlValuesRefetch: UseQueryResult<
|
||||
ErrorResponse | SuccessResponse<GetRetentionPeriodMetricsPayload>
|
||||
>['refetch'];
|
||||
tracesTtlValuesRefetch: UseQueryResult<
|
||||
ErrorResponse | SuccessResponse<GetRetentionPeriodTracesPayload>
|
||||
>['refetch'];
|
||||
}
|
||||
|
||||
export default GeneralSettings;
|
||||
|
69
frontend/src/container/GeneralSettings/StatusMessage.tsx
Normal file
69
frontend/src/container/GeneralSettings/StatusMessage.tsx
Normal file
@ -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 ? (
|
||||
<Card
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Row style={{ gap: '1rem', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Col xs={1}>
|
||||
<InfoCircleOutlined style={{ fontSize: '1.5rem' }} />
|
||||
</Col>
|
||||
|
||||
<Col
|
||||
xs={22}
|
||||
style={{
|
||||
color: messageColor,
|
||||
}}
|
||||
>
|
||||
{statusMessage}
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : null;
|
||||
}
|
||||
|
||||
interface StatusMessageProps {
|
||||
status: TStatus;
|
||||
total_retention: number | undefined;
|
||||
s3_retention: number | undefined;
|
||||
}
|
||||
export default StatusMessage;
|
@ -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<T extends TTTLType> = Promise<
|
||||
SuccessResponse<GetRetentionPeriodAPIPayloadProps<T>> | 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 (
|
||||
<Typography>
|
||||
{getRetentionPeriodApiResponse.data?.error ||
|
||||
{getRetentionPeriodMetricsApiResponse.data?.error ||
|
||||
getDisksResponse.data?.error ||
|
||||
t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// Error State - When RetentionPeriodTracesApi or getDiskApi gets errored out.
|
||||
if (getRetentionPeriodTracesApiResponse.isError || getDisksResponse.isError) {
|
||||
return (
|
||||
<Typography>
|
||||
{getRetentionPeriodTracesApiResponse.data?.error ||
|
||||
getDisksResponse.data?.error ||
|
||||
t('something_went_wrong')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 <Spinner tip="Loading.." height="70vh" />;
|
||||
}
|
||||
@ -44,7 +79,10 @@ function GeneralSettings(): JSX.Element {
|
||||
<GeneralSettingsContainer
|
||||
{...{
|
||||
getAvailableDiskPayload: getDisksResponse.data?.payload,
|
||||
ttlValuesPayload: getRetentionPeriodApiResponse.data?.payload,
|
||||
metricsTtlValuesPayload: getRetentionPeriodMetricsApiResponse.data?.payload,
|
||||
metricsTtlValuesRefetch: getRetentionPeriodMetricsApiResponse.refetch,
|
||||
tracesTtlValuesPayload: getRetentionPeriodTracesApiResponse.data?.payload,
|
||||
tracesTtlValuesRefetch: getRetentionPeriodTracesApiResponse.refetch,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -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%;
|
||||
`;
|
||||
|
@ -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' : ''
|
||||
}`;
|
||||
};
|
||||
|
1
frontend/src/types/api/settings/common.ts
Normal file
1
frontend/src/types/api/settings/common.ts
Normal file
@ -0,0 +1 @@
|
||||
export type TTTLType = 'metrics' | 'traces';
|
@ -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> = T extends 'metrics'
|
||||
? PayloadPropsMetrics
|
||||
: T extends 'traces'
|
||||
? PayloadPropsTraces
|
||||
: never;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { TTTLType } from './common';
|
||||
|
||||
export interface Props {
|
||||
type: 'metrics' | 'traces';
|
||||
type: TTTLType;
|
||||
totalDuration: string;
|
||||
coldStorage?: 's3' | null;
|
||||
toColdDuration?: string;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user