diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 064c09dbbd..c8bf9c9037 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -146,7 +146,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.37.2 + image: signoz/query-service:0.38.0 command: [ "-config=/root/config/prometheus.yml", @@ -186,7 +186,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:0.37.2 + image: signoz/frontend:0.38.0 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 9877c85ee8..0e04d69cf8 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -164,7 +164,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.37.2} + image: signoz/query-service:${DOCKER_TAG:-0.38.0} container_name: signoz-query-service command: [ @@ -203,7 +203,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.37.2} + image: signoz/frontend:${DOCKER_TAG:-0.38.0} container_name: signoz-frontend restart: on-failure depends_on: diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index ddb617188f..22f3ee67c2 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -152,9 +152,9 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost) // PAT APIs - router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.createPAT)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/pat/{id}", am.AdminAccess(ah.deletePAT)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost) router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet) diff --git a/ee/query-service/app/api/license.go b/ee/query-service/app/api/license.go index e382461d54..c6fe43a6bb 100644 --- a/ee/query-service/app/api/license.go +++ b/ee/query-service/app/api/license.go @@ -40,6 +40,7 @@ type billingDetails struct { BillingPeriodEnd int64 `json:"billingPeriodEnd"` Details details `json:"details"` Discount float64 `json:"discount"` + SubscriptionStatus string `json:"subscriptionStatus"` } `json:"data"` } diff --git a/frontend/public/locales/en-GB/alerts.json b/frontend/public/locales/en-GB/alerts.json index 98e9780e6a..5b102e147d 100644 --- a/frontend/public/locales/en-GB/alerts.json +++ b/frontend/public/locales/en-GB/alerts.json @@ -62,6 +62,7 @@ "button_cancel": "No", "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", + "field_notification_channel": "Notification Channel", "field_alert_desc": "Alert Description", "field_labels": "Labels", "field_severity": "Severity", @@ -100,7 +101,7 @@ "user_guide_ch_step3a": "Set alert severity, name and descriptions", "user_guide_ch_step3b": "Add tags to the alert in the Label field if needed", "user_tooltip_more_help": "More details on how to create alerts", - "choose_alert_type": "Choose a type for the alert:", + "choose_alert_type": "Choose a type for the alert", "metric_based_alert": "Metric based Alert", "metric_based_alert_desc": "Send a notification when a condition occurs in the metric data", "log_based_alert": "Log-based Alert", diff --git a/frontend/public/locales/en-GB/rules.json b/frontend/public/locales/en-GB/rules.json index c913dc08e0..9d55a0ba0f 100644 --- a/frontend/public/locales/en-GB/rules.json +++ b/frontend/public/locales/en-GB/rules.json @@ -54,6 +54,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", diff --git a/frontend/public/locales/en/alerts.json b/frontend/public/locales/en/alerts.json index 98e9780e6a..455ade61e3 100644 --- a/frontend/public/locales/en/alerts.json +++ b/frontend/public/locales/en/alerts.json @@ -63,6 +63,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", @@ -100,7 +101,7 @@ "user_guide_ch_step3a": "Set alert severity, name and descriptions", "user_guide_ch_step3b": "Add tags to the alert in the Label field if needed", "user_tooltip_more_help": "More details on how to create alerts", - "choose_alert_type": "Choose a type for the alert:", + "choose_alert_type": "Choose a type for the alert", "metric_based_alert": "Metric based Alert", "metric_based_alert_desc": "Send a notification when a condition occurs in the metric data", "log_based_alert": "Log-based Alert", diff --git a/frontend/public/locales/en/rules.json b/frontend/public/locales/en/rules.json index c913dc08e0..9d55a0ba0f 100644 --- a/frontend/public/locales/en/rules.json +++ b/frontend/public/locales/en/rules.json @@ -54,6 +54,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index 90cc588c47..a7d0396a50 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -73,12 +73,19 @@ function ResizeTable({ } }, [columns]); + const paginationConfig = { + hideOnSinglePage: true, + showTotal: (total: number, range: number[]): string => + `${range[0]}-${range[1]} of ${total} items`, + ...tableParams.pagination, + }; + return onDragColumn ? ( - +
) : ( -
+
); } diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx index 65a5914fca..60bf658e5d 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx @@ -1,4 +1,4 @@ -import { Row } from 'antd'; +import { Row, Typography } from 'antd'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertTypes } from 'types/api/alerts/alertTypes'; @@ -33,7 +33,14 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element { return ( -

{t('choose_alert_type')}

+ + {t('choose_alert_type')} + {renderOptions}
); diff --git a/frontend/src/container/FormAlertRules/BasicInfo.tsx b/frontend/src/container/FormAlertRules/BasicInfo.tsx index 3d96e7fb79..54877f5e0b 100644 --- a/frontend/src/container/FormAlertRules/BasicInfo.tsx +++ b/frontend/src/container/FormAlertRules/BasicInfo.tsx @@ -1,4 +1,5 @@ -import { Form, Select } from 'antd'; +import { Form, Select, Switch } from 'antd'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertDef, Labels } from 'types/api/alerts/def'; import { requireErrorMessage } from 'utils/form/requireErrorMessage'; @@ -7,7 +8,6 @@ import { popupContainer } from 'utils/selectPopupContainer'; import ChannelSelect from './ChannelSelect'; import LabelSelect from './labels'; import { - ChannelSelectTip, FormContainer, FormItemMedium, InputSmall, @@ -19,14 +19,41 @@ import { const { Option } = Select; interface BasicInfoProps { + isNewRule: boolean; alertDef: AlertDef; setAlertDef: (a: AlertDef) => void; } -function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element { - // init namespace for translations +function BasicInfo({ + isNewRule, + alertDef, + setAlertDef, +}: BasicInfoProps): JSX.Element { const { t } = useTranslation('alerts'); + const [ + shouldBroadCastToAllChannels, + setShouldBroadCastToAllChannels, + ] = useState(false); + + useEffect(() => { + const hasPreferredChannels = + (alertDef.preferredChannels && alertDef.preferredChannels.length > 0) || + isNewRule; + + setShouldBroadCastToAllChannels(!hasPreferredChannels); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleBroadcastToAllChannels = (shouldBroadcast: boolean): void => { + setShouldBroadCastToAllChannels(shouldBroadcast); + + setAlertDef({ + ...alertDef, + broadcastToAll: shouldBroadcast, + }); + }; + return ( <> {t('alert_form_step3')} @@ -105,18 +132,38 @@ function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element { initialValues={alertDef.labels} /> - - { - setAlertDef({ - ...alertDef, - preferredChannels, - }); - }} + + + - {t('channel_select_tooltip')} + + {!shouldBroadCastToAllChannels && ( + + { + setAlertDef({ + ...alertDef, + preferredChannels, + }); + }} + /> + + )} ); diff --git a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx index c2d78e661c..15f391c7cf 100644 --- a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx +++ b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx @@ -8,11 +8,13 @@ import { useTranslation } from 'react-i18next'; import { StyledSelect } from './styles'; export interface ChannelSelectProps { + disabled?: boolean; currentValue?: string[]; onSelectChannels: (s: string[]) => void; } function ChannelSelect({ + disabled, currentValue, onSelectChannels, }: ChannelSelectProps): JSX.Element | null { @@ -52,6 +54,7 @@ function ChannelSelect({ }; return ( { - setAlertDef(initialValue); - }, [initialValue]); + const broadcastToSpecificChannels = + (initialValue && + initialValue.preferredChannels && + initialValue.preferredChannels.length > 0) || + isNewRule; + + setAlertDef({ + ...initialValue, + broadcastToAll: !broadcastToSpecificChannels, + }); + }, [initialValue, isNewRule]); useEffect(() => { // Set selectedQueryName based on the length of queryOptions @@ -243,6 +255,7 @@ function FormAlertRules({ const preparePostData = (): AlertDef => { const postableAlert: AlertDef = { ...alertDef, + preferredChannels: alertDef.broadcastToAll ? [] : alertDef.preferredChannels, alertType, source: window?.location.toString(), ruleType: @@ -386,7 +399,11 @@ function FormAlertRules({ }, [t, isFormValid, memoizedPreparePostData, notifications]); const renderBasicInfo = (): JSX.Element => ( - + ); const renderQBChartPreview = (): JSX.Element => ( @@ -421,8 +438,6 @@ function FormAlertRules({ /> ); - const isNewRule = ruleId === 0; - const isAlertNameMissing = !formInstance.getFieldValue('alert'); const isAlertAvialableToSave = @@ -442,6 +457,10 @@ function FormAlertRules({ })); }; + const isChannelConfigurationValid = + alertDef?.broadcastToAll || + (alertDef.preferredChannels && alertDef.preferredChannels.length > 0); + return ( <> {Element} @@ -489,7 +508,11 @@ function FormAlertRules({ type="primary" onClick={onSaveHandler} icon={} - disabled={isAlertNameMissing || isAlertAvialableToSave} + disabled={ + isAlertNameMissing || + isAlertAvialableToSave || + !isChannelConfigurationValid + } > {isNewRule ? t('button_createrule') : t('button_savechanges')} @@ -497,6 +520,7 @@ function FormAlertRules({ diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index f77f714b26..0af1b9474f 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -124,6 +124,7 @@ function WidgetGraphComponent({ if (setSelectedDashboard && updatedDashboard.payload) { setSelectedDashboard(updatedDashboard.payload); } + setDeleteModal(false); featureResponse.refetch(); }, onError: () => { @@ -255,6 +256,7 @@ function WidgetGraphComponent({ destroyOnClose onCancel={onDeleteModelHandler} open={deleteModal} + confirmLoading={updateDashboardMutation.isLoading} title="Delete" height="10vh" onOk={onDeleteHandler} diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss index ab90890541..f65cda37cc 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss +++ b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss @@ -1,8 +1,10 @@ .fullscreen-grid-container { overflow: auto; + margin-top: 1rem; .react-grid-layout { border: none !important; + margin-top: 0; } } @@ -13,3 +15,9 @@ height: calc(100% - 30px); } } + +.lightMode { + .fullscreen-grid-container { + background-color: rgb(250, 250, 250); + } +} diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index 80221a3ef8..b4f1dbe19c 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -55,7 +55,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { const isDarkMode = useIsDarkMode(); - const [dashboardLayout, setDashboardLayout] = useState(layouts); + const [dashboardLayout, setDashboardLayout] = useState([]); const updateDashboardMutation = useUpdateDashboard(); @@ -77,6 +77,10 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { userRole, ); + useEffect(() => { + setDashboardLayout(layouts); + }, [layouts]); + const onSaveHandler = (): void => { if (!selectedDashboard) return; diff --git a/frontend/src/container/GridCardLayout/index.tsx b/frontend/src/container/GridCardLayout/index.tsx index a54daa313c..a2a82dc8ad 100644 --- a/frontend/src/container/GridCardLayout/index.tsx +++ b/frontend/src/container/GridCardLayout/index.tsx @@ -1,21 +1,14 @@ import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useCallback } from 'react'; -import { Layout } from 'react-grid-layout'; -import { EMPTY_WIDGET_LAYOUT } from './config'; import GraphLayoutContainer from './GridCardLayout'; function GridGraph(): JSX.Element { - const { handleToggleDashboardSlider, setLayouts } = useDashboard(); + const { handleToggleDashboardSlider } = useDashboard(); const onEmptyWidgetHandler = useCallback(() => { handleToggleDashboardSlider(true); - - setLayouts((preLayout: Layout[]) => [ - EMPTY_WIDGET_LAYOUT, - ...(preLayout || []), - ]); - }, [handleToggleDashboardSlider, setLayouts]); + }, [handleToggleDashboardSlider]); return ; } diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss new file mode 100644 index 0000000000..afd7a87d22 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss @@ -0,0 +1,5 @@ +.delete-modal { + .ant-modal-confirm-body { + align-items: center; + } +} diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index 810b99a278..2791d365b8 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,3 +1,5 @@ +import './DeleteButton.styles.scss'; + import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { Modal, Tooltip, Typography } from 'antd'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; @@ -64,6 +66,7 @@ function DeleteButton({ okText: 'Delete', okButtonProps: { danger: true }, centered: true, + className: 'delete-modal', }); }, [modal, name, deleteDashboardMutation, notifications, t, queryClient]); diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss b/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss index 1d91614ff6..9767c183c3 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/DashboardVariableSelection.styles.scss @@ -6,3 +6,11 @@ text-overflow: ellipsis; color: gray; } + +.variable-item { + .variable-select { + .ant-select-dropdown { + max-width: 300px; + } + } +} diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx index e636ced76c..339210f956 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx @@ -5,15 +5,16 @@ import { WarningOutlined } from '@ant-design/icons'; import { Input, Popover, Select, Tooltip, Typography } from 'antd'; import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import useDebounce from 'hooks/useDebounce'; import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; import sortValues from 'lib/dashbaordVariables/sortVariableValues'; +import { debounce } from 'lodash-es'; import map from 'lodash-es/map'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { memo, useEffect, useMemo, useState } from 'react'; import { useQuery } from 'react-query'; import { IDashboardVariable } from 'types/api/dashboard/getAll'; import { VariableResponseProps } from 'types/api/dashboard/variables/query'; +import { popupContainer } from 'utils/selectPopupContainer'; import { variablePropsToPayloadVariables } from '../utils'; import { SelectItemStyle, VariableContainer, VariableValue } from './styles'; @@ -44,6 +45,7 @@ const getSelectValue = ( return selectedValue?.toString() || ''; }; +// eslint-disable-next-line sonarjs/cognitive-complexity function VariableItem({ variableData, existingVariables, @@ -55,24 +57,8 @@ function VariableItem({ [], ); - const [variableValue, setVaribleValue] = useState( - variableData?.selectedValue?.toString() || '', - ); - - const debouncedVariableValue = useDebounce(variableValue, 500); - const [errorMessage, setErrorMessage] = useState(null); - useEffect(() => { - const { selectedValue } = variableData; - - if (selectedValue) { - setVaribleValue(selectedValue?.toString()); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [variableData]); - const getDependentVariables = (queryValue: string): string[] => { const matches = queryValue.match(variableRegexPattern); @@ -92,7 +78,12 @@ function VariableItem({ const variableName = variableData.name || ''; dependentVariables?.forEach((element) => { - dependentVariablesStr += `${element}${existingVariables[element]?.selectedValue}`; + const [, variable] = + Object.entries(existingVariables).find( + ([, value]) => value.name === element, + ) || []; + + dependentVariablesStr += `${element}${variable?.selectedValue}`; }); const variableKey = dependentVariablesStr.replace(/\s/g, ''); @@ -204,6 +195,9 @@ function VariableItem({ } }; + // do not debounce the above function as we do not need debounce in select variables + const debouncedHandleChange = debounce(handleChange, 500); + const { selectedValue } = variableData; const selectedValueStringified = useMemo(() => getSelectValue(selectedValue), [ selectedValue, @@ -219,14 +213,6 @@ function VariableItem({ : undefined; const enableSelectAll = variableData.multiSelect && variableData.showALLOption; - useEffect(() => { - if (debouncedVariableValue !== variableData?.selectedValue?.toString()) { - handleChange(debouncedVariableValue); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debouncedVariableValue]); - useEffect(() => { // Fetch options for CUSTOM Type if (variableData.type === 'CUSTOM') { @@ -240,7 +226,7 @@ function VariableItem({ placement="top" title={isDashboardLocked ? 'Dashboard is locked' : ''} > - + ${variableData.name} @@ -250,9 +236,10 @@ function VariableItem({ placeholder="Enter value" disabled={isDashboardLocked} bordered={false} - value={variableValue} + key={variableData.selectedValue?.toString()} + defaultValue={variableData.selectedValue?.toString()} onChange={(e): void => { - setVaribleValue(e.target.value || ''); + debouncedHandleChange(e.target.value || ''); }} style={{ width: @@ -263,18 +250,25 @@ function VariableItem({ !errorMessage && optionsData && (