mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-17 07:45:53 +08:00
feat: feature flag is updated (#2666)
* feat: flag is updated * feat: feature flag is updated * feat: onrefetch is added on several actions on app * chore: tab is updated * chore: creating dashbaord error is handled * fix: message is fixed * chore: jest test is updated
This commit is contained in:
parent
e7f5adc8a9
commit
604d98be05
@ -1,23 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps } from 'types/api/features/getFeatures';
|
|
||||||
|
|
||||||
const getFeaturesFlags = async (): Promise<
|
|
||||||
SuccessResponse<PayloadProps> | ErrorResponse
|
|
||||||
> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.get(`/featureFlags`);
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getFeaturesFlags;
|
|
@ -1,7 +0,0 @@
|
|||||||
// keep this consistent with backend model>features.go
|
|
||||||
export enum FeatureKeys {
|
|
||||||
SSO = 'SSO',
|
|
||||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
|
||||||
BASIC_PLAN = 'BASIC_PLAN',
|
|
||||||
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
|
||||||
}
|
|
@ -1,6 +1,11 @@
|
|||||||
// keep this consistent with backend constants.go
|
// keep this consistent with backend constants.go
|
||||||
export enum FeatureKeys {
|
export enum FeatureKeys {
|
||||||
SSO = 'SSO',
|
SSO = 'SSO',
|
||||||
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
|
DurationSort = 'DurationSort',
|
||||||
BASIC_PLAN = 'BASIC_PLAN',
|
TimestampSort = 'TimestampSort',
|
||||||
|
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
|
||||||
|
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
|
||||||
|
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
|
||||||
|
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
|
||||||
|
DISABLE_UPSELL = 'DISABLE_UPSELL',
|
||||||
}
|
}
|
||||||
|
3
frontend/src/constants/reactQueryKeys.ts
Normal file
3
frontend/src/constants/reactQueryKeys.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const REACT_QUERY_KEY = {
|
||||||
|
GET_ALL_LICENCES: 'GET_ALL_LICENCES',
|
||||||
|
};
|
@ -18,7 +18,7 @@ import {
|
|||||||
UPDATE_CONFIGS,
|
UPDATE_CONFIGS,
|
||||||
UPDATE_CURRENT_ERROR,
|
UPDATE_CURRENT_ERROR,
|
||||||
UPDATE_CURRENT_VERSION,
|
UPDATE_CURRENT_VERSION,
|
||||||
UPDATE_FEATURE_FLAGS,
|
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
UPDATE_LATEST_VERSION,
|
UPDATE_LATEST_VERSION,
|
||||||
UPDATE_LATEST_VERSION_ERROR,
|
UPDATE_LATEST_VERSION_ERROR,
|
||||||
} from 'types/actions/app';
|
} from 'types/actions/app';
|
||||||
@ -129,19 +129,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
message: t('oops_something_went_wrong_version'),
|
message: t('oops_something_went_wrong_version'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
getFeaturesResponse.isFetched &&
|
|
||||||
getFeaturesResponse.isSuccess &&
|
|
||||||
getFeaturesResponse.data &&
|
|
||||||
getFeaturesResponse.data.payload
|
|
||||||
) {
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_FEATURE_FLAGS,
|
|
||||||
payload: {
|
|
||||||
...getFeaturesResponse.data.payload,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
getUserVersionResponse.isFetched &&
|
getUserVersionResponse.isFetched &&
|
||||||
@ -173,20 +160,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
getFeaturesResponse.isFetched &&
|
|
||||||
getFeaturesResponse.isSuccess &&
|
|
||||||
getFeaturesResponse.data &&
|
|
||||||
getFeaturesResponse.data.payload
|
|
||||||
) {
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_FEATURE_FLAGS,
|
|
||||||
payload: {
|
|
||||||
...getFeaturesResponse.data.payload,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
getDynamicConfigsResponse.isFetched &&
|
getDynamicConfigsResponse.isFetched &&
|
||||||
getDynamicConfigsResponse.isSuccess &&
|
getDynamicConfigsResponse.isSuccess &&
|
||||||
@ -226,6 +199,29 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
notifications,
|
notifications,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
getFeaturesResponse.isFetched &&
|
||||||
|
getFeaturesResponse.isSuccess &&
|
||||||
|
getFeaturesResponse.data &&
|
||||||
|
getFeaturesResponse.data.payload
|
||||||
|
) {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
|
payload: {
|
||||||
|
featureFlag: getFeaturesResponse.data.payload,
|
||||||
|
refetch: getFeaturesResponse.refetch,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
dispatch,
|
||||||
|
getFeaturesResponse.data,
|
||||||
|
getFeaturesResponse.isFetched,
|
||||||
|
getFeaturesResponse.isSuccess,
|
||||||
|
getFeaturesResponse.refetch,
|
||||||
|
]);
|
||||||
|
|
||||||
const isToDisplayLayout = isLoggedIn;
|
const isToDisplayLayout = isLoggedIn;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -36,8 +36,16 @@ export const alertDefaults: AlertDef = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
promQueries: {},
|
promQueries: {},
|
||||||
chQueries: {},
|
chQueries: {
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
A: {
|
||||||
|
name: 'A',
|
||||||
|
query: ``,
|
||||||
|
rawQuery: ``,
|
||||||
|
legend: '',
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
queryType: EQueryType.CLICKHOUSE,
|
||||||
panelType: PANEL_TYPES.TIME_SERIES,
|
panelType: PANEL_TYPES.TIME_SERIES,
|
||||||
},
|
},
|
||||||
op: defaultCompareOp,
|
op: defaultCompareOp,
|
||||||
|
@ -2,7 +2,7 @@ import { Button, Tabs } from 'antd';
|
|||||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { QueryBuilder } from 'container/QueryBuilder';
|
import { QueryBuilder } from 'container/QueryBuilder';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||||
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
|
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
|
||||||
@ -91,11 +91,14 @@ function QuerySection({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const items = [
|
const items = useMemo(
|
||||||
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
|
() => [
|
||||||
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
|
{ label: t('tab_qb'), key: EQueryType.QUERY_BUILDER },
|
||||||
{ label: t('tab_promql'), key: EQueryType.PROM },
|
{ label: t('tab_chquery'), key: EQueryType.CLICKHOUSE },
|
||||||
];
|
{ label: t('tab_promql'), key: EQueryType.PROM },
|
||||||
|
],
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
|
const renderTabs = (typ: AlertTypes): JSX.Element | null => {
|
||||||
switch (typ) {
|
switch (typ) {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
import { Col, FormInstance, Modal, Typography } from 'antd';
|
import { Col, FormInstance, Modal, Tooltip, Typography } from 'antd';
|
||||||
import saveAlertApi from 'api/alerts/save';
|
import saveAlertApi from 'api/alerts/save';
|
||||||
import testAlertApi from 'api/alerts/testAlert';
|
import testAlertApi from 'api/alerts/testAlert';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
|
||||||
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||||
|
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||||
@ -145,6 +147,7 @@ function FormAlertRules({
|
|||||||
// onQueryCategoryChange handles changes to query category
|
// onQueryCategoryChange handles changes to query category
|
||||||
// in state as well as sets additional defaults
|
// in state as well as sets additional defaults
|
||||||
const onQueryCategoryChange = (val: EQueryType): void => {
|
const onQueryCategoryChange = (val: EQueryType): void => {
|
||||||
|
console.log('onQueryCategoryChange', val);
|
||||||
setQueryCategory(val);
|
setQueryCategory(val);
|
||||||
if (val === EQueryType.PROM) {
|
if (val === EQueryType.PROM) {
|
||||||
setAlertDef({
|
setAlertDef({
|
||||||
@ -298,6 +301,10 @@ function FormAlertRules({
|
|||||||
initQuery,
|
initQuery,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isAlertAvialable = useIsFeatureDisabled(
|
||||||
|
FeatureKeys.QUERY_BUILDER_ALERTS,
|
||||||
|
);
|
||||||
|
|
||||||
const saveRule = useCallback(async () => {
|
const saveRule = useCallback(async () => {
|
||||||
if (!isFormValid()) {
|
if (!isFormValid()) {
|
||||||
return;
|
return;
|
||||||
@ -437,6 +444,12 @@ function FormAlertRules({
|
|||||||
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
selectedInterval={toChartInterval(alertDef.evalWindow)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isNewRule = ruleId === 0;
|
||||||
|
|
||||||
|
const isAlertAvialableToSave =
|
||||||
|
isAlertAvialable && isNewRule && queryCategory === EQueryType.QUERY_BUILDER;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Element}
|
{Element}
|
||||||
@ -469,14 +482,18 @@ function FormAlertRules({
|
|||||||
|
|
||||||
{renderBasicInfo()}
|
{renderBasicInfo()}
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<ActionButton
|
<Tooltip title={isAlertAvialableToSave ? MESSAGE.ALERT : ''}>
|
||||||
loading={loading || false}
|
<ActionButton
|
||||||
type="primary"
|
loading={loading || false}
|
||||||
onClick={onSaveHandler}
|
type="primary"
|
||||||
icon={<SaveOutlined />}
|
onClick={onSaveHandler}
|
||||||
>
|
icon={<SaveOutlined />}
|
||||||
{ruleId > 0 ? t('button_savechanges') : t('button_createrule')}
|
disabled={isAlertAvialableToSave}
|
||||||
</ActionButton>
|
>
|
||||||
|
{isNewRule ? t('button_createrule') : t('button_savechanges')}
|
||||||
|
</ActionButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
loading={loading || false}
|
loading={loading || false}
|
||||||
type="default"
|
type="default"
|
||||||
|
@ -2,12 +2,14 @@ import { Typography } from 'antd';
|
|||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridGraphComponent from 'container/GridGraphComponent';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import usePreviousValue from 'hooks/usePreviousValue';
|
import usePreviousValue from 'hooks/usePreviousValue';
|
||||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
import getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
import isEmpty from 'lodash-es/isEmpty';
|
import isEmpty from 'lodash-es/isEmpty';
|
||||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
@ -20,8 +22,8 @@ import {
|
|||||||
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { GlobalTime } from 'types/actions/globalTime';
|
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
import DashboardReducer from 'types/reducer/dashboards';
|
import DashboardReducer from 'types/reducer/dashboards';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
@ -46,18 +48,22 @@ function GridCardGraph({
|
|||||||
initialInView: true,
|
initialInView: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
const [deleteModal, setDeleteModal] = useState(false);
|
const [deleteModal, setDeleteModal] = useState(false);
|
||||||
|
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
(state) => state.globalTime,
|
|
||||||
);
|
|
||||||
const { selectedTime: globalSelectedInterval } = useSelector<
|
|
||||||
AppState,
|
AppState,
|
||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
const { dashboards } = useSelector<AppState, DashboardReducer>(
|
||||||
(state) => state.dashboards,
|
(state) => state.dashboards,
|
||||||
);
|
);
|
||||||
@ -122,9 +128,27 @@ function GridCardGraph({
|
|||||||
|
|
||||||
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
|
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
|
||||||
|
|
||||||
deleteWidget({ widgetId, setLayout });
|
featureResponse
|
||||||
onToggleModal(setDeleteModal);
|
.refetch()
|
||||||
}, [deleteWidget, layout, onToggleModal, setLayout, widget]);
|
.then(() => {
|
||||||
|
deleteWidget({ widgetId, setLayout });
|
||||||
|
onToggleModal(setDeleteModal);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notifications.error({
|
||||||
|
message: t('common:something_went_wrong'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
widget,
|
||||||
|
layout,
|
||||||
|
featureResponse,
|
||||||
|
deleteWidget,
|
||||||
|
setLayout,
|
||||||
|
onToggleModal,
|
||||||
|
notifications,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
const getModals = (): JSX.Element => (
|
const getModals = (): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
|
import useFeatureFlag, { MESSAGE } from 'hooks/useFeatureFlag';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { Layout } from 'react-grid-layout';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
@ -14,6 +17,7 @@ import {
|
|||||||
ButtonContainer,
|
ButtonContainer,
|
||||||
Card,
|
Card,
|
||||||
CardContainer,
|
CardContainer,
|
||||||
|
NoPanelAvialable,
|
||||||
ReactGridLayout,
|
ReactGridLayout,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
@ -35,6 +39,8 @@ function GraphLayout({
|
|||||||
role,
|
role,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const queryBuilderFeature = useFeatureFlag(FeatureKeys.QUERY_BUILDER_PANELS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
@ -74,9 +80,31 @@ function GraphLayout({
|
|||||||
onLayoutChange={onLayoutChangeHandler}
|
onLayoutChange={onLayoutChangeHandler}
|
||||||
draggableHandle=".drag-handle"
|
draggableHandle=".drag-handle"
|
||||||
>
|
>
|
||||||
{layouts.map(({ Component, ...rest }) => {
|
{layouts.map(({ Component, ...rest }, layoutIndex) => {
|
||||||
const currentWidget = (widgets || [])?.find((e) => e.id === rest.i);
|
const currentWidget = (widgets || [])?.find((e) => e.id === rest.i);
|
||||||
|
|
||||||
|
const usageLimit = queryBuilderFeature?.usage_limit || 0;
|
||||||
|
|
||||||
|
const isPanelNotAvialable = usageLimit > 0 && usageLimit <= layoutIndex;
|
||||||
|
|
||||||
|
if (isPanelNotAvialable) {
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
data-grid={rest}
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
key={currentWidget?.id}
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<Typography.Text type="danger">
|
||||||
|
<NoPanelAvialable isDarkMode={isDarkMode}>
|
||||||
|
{MESSAGE.WIDGET.replace('{{widget}}', usageLimit.toString())}
|
||||||
|
</NoPanelAvialable>
|
||||||
|
</Typography.Text>
|
||||||
|
</Card>
|
||||||
|
</CardContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer
|
<CardContainer
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
|
@ -84,6 +84,8 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
if (!isAddWidget) {
|
if (!isAddWidget) {
|
||||||
@ -119,6 +121,12 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
|
||||||
|
const errorMessage = t('common:something_went_wrong');
|
||||||
|
|
||||||
const onLayoutSaveHandler = useCallback(
|
const onLayoutSaveHandler = useCallback(
|
||||||
async (layout: Layout[]) => {
|
async (layout: Layout[]) => {
|
||||||
try {
|
try {
|
||||||
@ -128,44 +136,62 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
loading: true,
|
loading: true,
|
||||||
}));
|
}));
|
||||||
const updatedDashboard: Dashboard = {
|
|
||||||
...selectedDashboard,
|
featureResponse
|
||||||
data: {
|
.refetch()
|
||||||
title: data.title,
|
.then(async () => {
|
||||||
description: data.description,
|
const updatedDashboard: Dashboard = {
|
||||||
name: data.name,
|
...selectedDashboard,
|
||||||
tags: data.tags,
|
data: {
|
||||||
widgets: data.widgets,
|
title: data.title,
|
||||||
variables: data.variables,
|
description: data.description,
|
||||||
layout,
|
name: data.name,
|
||||||
},
|
tags: data.tags,
|
||||||
uuid: selectedDashboard.uuid,
|
widgets: data.widgets,
|
||||||
};
|
variables: data.variables,
|
||||||
// Save layout only when users has the has the permission to do so.
|
layout,
|
||||||
if (saveLayoutPermission) {
|
},
|
||||||
const response = await updateDashboardApi(updatedDashboard);
|
uuid: selectedDashboard.uuid,
|
||||||
if (response.statusCode === 200) {
|
};
|
||||||
setSaveLayoutState((state) => ({
|
// Save layout only when users has the has the permission to do so.
|
||||||
...state,
|
if (saveLayoutPermission) {
|
||||||
error: false,
|
const response = await updateDashboardApi(updatedDashboard);
|
||||||
errorMessage: '',
|
if (response.statusCode === 200) {
|
||||||
loading: false,
|
setSaveLayoutState((state) => ({
|
||||||
}));
|
...state,
|
||||||
dispatch({
|
error: false,
|
||||||
type: UPDATE_DASHBOARD,
|
errorMessage: '',
|
||||||
payload: updatedDashboard,
|
loading: false,
|
||||||
});
|
}));
|
||||||
} else {
|
dispatch({
|
||||||
|
type: UPDATE_DASHBOARD,
|
||||||
|
payload: updatedDashboard,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setSaveLayoutState((state) => ({
|
||||||
|
...state,
|
||||||
|
error: true,
|
||||||
|
errorMessage: response.error || errorMessage,
|
||||||
|
loading: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
setSaveLayoutState((state) => ({
|
setSaveLayoutState((state) => ({
|
||||||
...state,
|
...state,
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: response.error || 'Something went wrong',
|
errorMessage,
|
||||||
loading: false,
|
loading: false,
|
||||||
}));
|
}));
|
||||||
}
|
notifications.error({
|
||||||
}
|
message: errorMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
notifications.error({
|
||||||
|
message: errorMessage,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -176,6 +202,9 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
data.variables,
|
data.variables,
|
||||||
data.widgets,
|
data.widgets,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
errorMessage,
|
||||||
|
featureResponse,
|
||||||
|
notifications,
|
||||||
saveLayoutPermission,
|
saveLayoutPermission,
|
||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
],
|
],
|
||||||
@ -207,8 +236,6 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
[widgets, onDragSelect],
|
[widgets, onDragSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const onEmptyWidgetHandler = useCallback(async () => {
|
const onEmptyWidgetHandler = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const id = 'empty';
|
const id = 'empty';
|
||||||
@ -239,10 +266,10 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
setLayoutFunction(layout);
|
setLayoutFunction(layout);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: error instanceof Error ? error.toString() : 'Something went wrong',
|
message: error instanceof Error ? error.toString() : errorMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data, selectedDashboard, setLayoutFunction, notifications]);
|
}, [data, selectedDashboard, setLayoutFunction, notifications, errorMessage]);
|
||||||
|
|
||||||
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
const onLayoutChangeHandler = async (layout: Layout[]): Promise<void> => {
|
||||||
setLayoutFunction(layout);
|
setLayoutFunction(layout);
|
||||||
@ -253,43 +280,57 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
const onAddPanelHandler = useCallback(() => {
|
const onAddPanelHandler = useCallback(() => {
|
||||||
try {
|
try {
|
||||||
setAddPanelLoading(true);
|
setAddPanelLoading(true);
|
||||||
const isEmptyLayoutPresent =
|
featureResponse
|
||||||
layouts.find((e) => e.i === 'empty') !== undefined;
|
.refetch()
|
||||||
|
.then(() => {
|
||||||
|
const isEmptyLayoutPresent =
|
||||||
|
layouts.find((e) => e.i === 'empty') !== undefined;
|
||||||
|
|
||||||
if (!isEmptyLayoutPresent) {
|
if (!isEmptyLayoutPresent) {
|
||||||
onEmptyWidgetHandler()
|
onEmptyWidgetHandler()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setAddPanelLoading(false);
|
setAddPanelLoading(false);
|
||||||
|
toggleAddWidget(true);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notifications.error({
|
||||||
|
message: errorMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
toggleAddWidget(true);
|
toggleAddWidget(true);
|
||||||
})
|
setAddPanelLoading(false);
|
||||||
.catch(() => {
|
}
|
||||||
notifications.error(t('something_went_wrong'));
|
})
|
||||||
});
|
.catch(() =>
|
||||||
} else {
|
notifications.error({
|
||||||
toggleAddWidget(true);
|
message: errorMessage,
|
||||||
setAddPanelLoading(false);
|
}),
|
||||||
}
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (typeof error === 'string') {
|
notifications.error({
|
||||||
notifications.error({
|
message: errorMessage,
|
||||||
message: error || t('something_went_wrong'),
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [layouts, onEmptyWidgetHandler, t, toggleAddWidget, notifications]);
|
}, [
|
||||||
|
featureResponse,
|
||||||
|
layouts,
|
||||||
|
onEmptyWidgetHandler,
|
||||||
|
toggleAddWidget,
|
||||||
|
notifications,
|
||||||
|
errorMessage,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GraphLayoutContainer
|
<GraphLayoutContainer
|
||||||
{...{
|
addPanelLoading={addPanelLoading}
|
||||||
addPanelLoading,
|
layouts={layouts}
|
||||||
layouts,
|
onAddPanelHandler={onAddPanelHandler}
|
||||||
onAddPanelHandler,
|
onLayoutChangeHandler={onLayoutChangeHandler}
|
||||||
onLayoutChangeHandler,
|
onLayoutSaveHandler={onLayoutSaveHandler}
|
||||||
onLayoutSaveHandler,
|
saveLayoutState={saveLayoutState}
|
||||||
saveLayoutState,
|
setLayout={setLayout}
|
||||||
widgets,
|
widgets={widgets}
|
||||||
setLayout,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -79,3 +79,11 @@ export const Button = styled(ButtonComponent)`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const NoPanelAvialable = styled.div<Props>`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Typography } from 'antd';
|
import { Spin, Typography } from 'antd';
|
||||||
import { FeatureKeys } from 'constants/features';
|
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import useFeatureFlags from 'hooks/useFeatureFlag';
|
import useLicense, { LICENSE_PLAN_KEY } from 'hooks/useLicense';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@ -12,7 +11,22 @@ import {
|
|||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
|
function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
|
||||||
const isEnterprise = useFeatureFlags(FeatureKeys.ENTERPRISE_PLAN);
|
const { data, isLoading } = useLicense();
|
||||||
|
|
||||||
|
const onManageLicense = (): void => {
|
||||||
|
onToggle();
|
||||||
|
history.push(ROUTES.LIST_LICENSES);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading || data?.payload === undefined) {
|
||||||
|
return <Spin />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEnterprise = data?.payload?.some(
|
||||||
|
(license) =>
|
||||||
|
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.ENTERPRISE_PLAN,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Typography>SIGNOZ STATUS</Typography>
|
<Typography>SIGNOZ STATUS</Typography>
|
||||||
@ -23,14 +37,7 @@ function ManageLicense({ onToggle }: ManageLicenseProps): JSX.Element {
|
|||||||
<Typography>{!isEnterprise ? 'Free Plan' : 'Enterprise Plan'} </Typography>
|
<Typography>{!isEnterprise ? 'Free Plan' : 'Enterprise Plan'} </Typography>
|
||||||
</ManageLicenseWrapper>
|
</ManageLicenseWrapper>
|
||||||
|
|
||||||
<Typography.Link
|
<Typography.Link onClick={onManageLicense}>Manage Licenses</Typography.Link>
|
||||||
onClick={(): void => {
|
|
||||||
onToggle();
|
|
||||||
history.push(ROUTES.LIST_LICENSES);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Manage Licenses
|
|
||||||
</Typography.Link>
|
|
||||||
</ManageLicenseContainer>
|
</ManageLicenseContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { Button, Form, Input } from 'antd';
|
import { Button, Form, Input } from 'antd';
|
||||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
|
||||||
import apply from 'api/licenses/apply';
|
import apply from 'api/licenses/apply';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { QueryObserverResult, RefetchOptions, useQuery } from 'react-query';
|
import { QueryObserverResult, RefetchOptions } from 'react-query';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Dispatch } from 'redux';
|
import { AppState } from 'store/reducers';
|
||||||
import { AppAction, UPDATE_FEATURE_FLAGS } from 'types/actions/app';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import { PayloadProps } from 'types/api/licenses/getAll';
|
import { PayloadProps } from 'types/api/licenses/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import { ApplyForm, ApplyFormContainer, LicenseInput } from './styles';
|
import { ApplyForm, ApplyFormContainer, LicenseInput } from './styles';
|
||||||
|
|
||||||
@ -21,12 +20,9 @@ function ApplyLicenseForm({
|
|||||||
const { t } = useTranslation(['licenses']);
|
const { t } = useTranslation(['licenses']);
|
||||||
const [key, setKey] = useState('');
|
const [key, setKey] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const dispatch = useDispatch<Dispatch<AppAction>>();
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
const { refetch } = useQuery({
|
(state) => state.app,
|
||||||
queryFn: getFeaturesFlags,
|
);
|
||||||
queryKey: 'getFeatureFlags',
|
|
||||||
enabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
@ -47,16 +43,8 @@ function ApplyLicenseForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
const [featureFlagsResponse] = await Promise.all([
|
await Promise.all([featureResponse?.refetch(), licenseRefetch()]);
|
||||||
refetch(),
|
|
||||||
licenseRefetch(),
|
|
||||||
]);
|
|
||||||
if (featureFlagsResponse.data?.payload) {
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_FEATURE_FLAGS,
|
|
||||||
payload: featureFlagsResponse.data.payload,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('license_applied'),
|
description: t('license_applied'),
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
import { Tabs, Typography } from 'antd';
|
import { Tabs, Typography } from 'antd';
|
||||||
import getAll from 'api/licenses/getAll';
|
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
|
import useLicense from 'hooks/useLicense';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import ApplyLicenseForm from './ApplyLicenseForm';
|
import ApplyLicenseForm from './ApplyLicenseForm';
|
||||||
import ListLicenses from './ListLicenses';
|
import ListLicenses from './ListLicenses';
|
||||||
|
|
||||||
function Licenses(): JSX.Element {
|
function Licenses(): JSX.Element {
|
||||||
const { t } = useTranslation(['licenses']);
|
const { t } = useTranslation(['licenses']);
|
||||||
const { data, isError, isLoading, refetch } = useQuery({
|
const { data, isError, isLoading, refetch } = useLicense();
|
||||||
queryFn: getAll,
|
|
||||||
queryKey: 'getAllLicenses',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isError || data?.error) {
|
if (isError || data?.error) {
|
||||||
return <Typography>{data?.error}</Typography>;
|
return <Typography>{data?.error}</Typography>;
|
||||||
|
@ -2,8 +2,11 @@ import { NotificationInstance } from 'antd/es/notification/interface';
|
|||||||
import deleteAlerts from 'api/alerts/delete';
|
import deleteAlerts from 'api/alerts/delete';
|
||||||
import { State } from 'hooks/useFetch';
|
import { State } from 'hooks/useFetch';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
|
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
|
||||||
import { GettableAlert } from 'types/api/alerts/get';
|
import { GettableAlert } from 'types/api/alerts/get';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import { ColumnButton } from './styles';
|
import { ColumnButton } from './styles';
|
||||||
|
|
||||||
@ -22,15 +25,14 @@ function DeleteAlert({
|
|||||||
payload: undefined,
|
payload: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
|
||||||
const defaultErrorMessage = 'Something went wrong';
|
const defaultErrorMessage = 'Something went wrong';
|
||||||
|
|
||||||
const onDeleteHandler = async (id: number): Promise<void> => {
|
const onDeleteHandler = async (id: number): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setDeleteAlertState((state) => ({
|
|
||||||
...state,
|
|
||||||
loading: true,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const response = await deleteAlerts({
|
const response = await deleteAlerts({
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
@ -72,11 +74,32 @@ function DeleteAlert({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClickHandler = (): void => {
|
||||||
|
setDeleteAlertState((state) => ({
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
}));
|
||||||
|
featureResponse
|
||||||
|
.refetch()
|
||||||
|
.then(() => {
|
||||||
|
onDeleteHandler(id);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setDeleteAlertState((state) => ({
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
}));
|
||||||
|
notifications.error({
|
||||||
|
message: defaultErrorMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColumnButton
|
<ColumnButton
|
||||||
disabled={deleteAlertState.loading || false}
|
disabled={deleteAlertState.loading || false}
|
||||||
loading={deleteAlertState.loading || false}
|
loading={deleteAlertState.loading || false}
|
||||||
onClick={(): Promise<void> => onDeleteHandler(id)}
|
onClick={onClickHandler}
|
||||||
type="link"
|
type="link"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
|
@ -26,7 +26,9 @@ import ToggleAlertState from './ToggleAlertState';
|
|||||||
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||||
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
|
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
|
||||||
const { t } = useTranslation('common');
|
const { t } = useTranslation('common');
|
||||||
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { role, featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
const [addNewAlert, action] = useComponentPermission(
|
const [addNewAlert, action] = useComponentPermission(
|
||||||
['add_new_alert', 'action'],
|
['add_new_alert', 'action'],
|
||||||
role,
|
role,
|
||||||
@ -48,12 +50,28 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
|||||||
})();
|
})();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
|
const handleError = useCallback((): void => {
|
||||||
|
notificationsApi.error({
|
||||||
|
message: t('something_went_wrong'),
|
||||||
|
});
|
||||||
|
}, [notificationsApi, t]);
|
||||||
|
|
||||||
const onClickNewAlertHandler = useCallback(() => {
|
const onClickNewAlertHandler = useCallback(() => {
|
||||||
history.push(ROUTES.ALERTS_NEW);
|
featureResponse
|
||||||
}, []);
|
.refetch()
|
||||||
|
.then(() => {
|
||||||
|
history.push(ROUTES.ALERTS_NEW);
|
||||||
|
})
|
||||||
|
.catch(handleError);
|
||||||
|
}, [featureResponse, handleError]);
|
||||||
|
|
||||||
const onEditHandler = (id: string): void => {
|
const onEditHandler = (id: string): void => {
|
||||||
history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
|
featureResponse
|
||||||
|
.refetch()
|
||||||
|
.then(() => {
|
||||||
|
history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
|
||||||
|
})
|
||||||
|
.catch(handleError);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnsType<GettableAlert> = [
|
const columns: ColumnsType<GettableAlert> = [
|
||||||
|
@ -4,6 +4,7 @@ import { Button, Modal, Space, Typography, Upload, UploadProps } from 'antd';
|
|||||||
import createDashboard from 'api/dashboard/create';
|
import createDashboard from 'api/dashboard/create';
|
||||||
import Editor from 'components/Editor';
|
import Editor from 'components/Editor';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import { MESSAGE } from 'hooks/useFeatureFlag';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
@ -28,6 +29,8 @@ function ImportJSON({
|
|||||||
const [isCreateDashboardError, setIsCreateDashboardError] = useState<boolean>(
|
const [isCreateDashboardError, setIsCreateDashboardError] = useState<boolean>(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
const [isFeatureAlert, setIsFeatureAlert] = useState<boolean>(false);
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const [dashboardCreating, setDashboardCreating] = useState<boolean>(false);
|
const [dashboardCreating, setDashboardCreating] = useState<boolean>(false);
|
||||||
@ -99,6 +102,15 @@ function ImportJSON({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, 10);
|
}, 10);
|
||||||
|
} else if (response.error === 'feature usage exceeded') {
|
||||||
|
setIsFeatureAlert(true);
|
||||||
|
notifications.error({
|
||||||
|
message:
|
||||||
|
response.error ||
|
||||||
|
t('something_went_wrong', {
|
||||||
|
ns: 'common',
|
||||||
|
}),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsCreateDashboardError(true);
|
setIsCreateDashboardError(true);
|
||||||
notifications.error({
|
notifications.error({
|
||||||
@ -112,6 +124,7 @@ function ImportJSON({
|
|||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
} catch {
|
} catch {
|
||||||
setDashboardCreating(false);
|
setDashboardCreating(false);
|
||||||
|
setIsFeatureAlert(false);
|
||||||
|
|
||||||
setIsCreateDashboardError(true);
|
setIsCreateDashboardError(true);
|
||||||
}
|
}
|
||||||
@ -124,6 +137,13 @@ function ImportJSON({
|
|||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onCancelHandler = (): void => {
|
||||||
|
setIsUploadJSONError(false);
|
||||||
|
setIsCreateDashboardError(false);
|
||||||
|
setIsFeatureAlert(false);
|
||||||
|
onModalHandler();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={isImportJSONModalVisible}
|
open={isImportJSONModalVisible}
|
||||||
@ -131,7 +151,7 @@ function ImportJSON({
|
|||||||
maskClosable
|
maskClosable
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
width="70vw"
|
width="70vw"
|
||||||
onCancel={onModalHandler}
|
onCancel={onCancelHandler}
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
<Typography.Title level={4}>{t('import_json')}</Typography.Title>
|
||||||
@ -148,6 +168,11 @@ function ImportJSON({
|
|||||||
{t('load_json')}
|
{t('load_json')}
|
||||||
</Button>
|
</Button>
|
||||||
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
{isCreateDashboardError && getErrorNode(t('error_loading_json'))}
|
||||||
|
{isFeatureAlert && (
|
||||||
|
<Typography.Text type="danger">
|
||||||
|
{MESSAGE.CREATE_DASHBOARD}
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
</FooterContainer>
|
</FooterContainer>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
@ -74,49 +74,52 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns: TableColumnProps<Data>[] = [
|
const columns: TableColumnProps<Data>[] = useMemo(
|
||||||
{
|
() => [
|
||||||
title: 'Name',
|
{
|
||||||
dataIndex: 'name',
|
title: 'Name',
|
||||||
width: 100,
|
dataIndex: 'name',
|
||||||
render: Name,
|
width: 100,
|
||||||
},
|
render: Name,
|
||||||
{
|
|
||||||
title: 'Description',
|
|
||||||
width: 100,
|
|
||||||
dataIndex: 'description',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Tags (can be multiple)',
|
|
||||||
dataIndex: 'tags',
|
|
||||||
width: 80,
|
|
||||||
render: Tags,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Created At',
|
|
||||||
dataIndex: 'createdBy',
|
|
||||||
width: 80,
|
|
||||||
sorter: (a: Data, b: Data): number => {
|
|
||||||
const prev = new Date(a.createdBy).getTime();
|
|
||||||
const next = new Date(b.createdBy).getTime();
|
|
||||||
|
|
||||||
return prev - next;
|
|
||||||
},
|
},
|
||||||
render: Createdby,
|
{
|
||||||
},
|
title: 'Description',
|
||||||
{
|
width: 100,
|
||||||
title: 'Last Updated Time',
|
dataIndex: 'description',
|
||||||
width: 90,
|
|
||||||
dataIndex: 'lastUpdatedTime',
|
|
||||||
sorter: (a: Data, b: Data): number => {
|
|
||||||
const prev = new Date(a.lastUpdatedTime).getTime();
|
|
||||||
const next = new Date(b.lastUpdatedTime).getTime();
|
|
||||||
|
|
||||||
return prev - next;
|
|
||||||
},
|
},
|
||||||
render: DateComponent,
|
{
|
||||||
},
|
title: 'Tags (can be multiple)',
|
||||||
];
|
dataIndex: 'tags',
|
||||||
|
width: 80,
|
||||||
|
render: Tags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Created At',
|
||||||
|
dataIndex: 'createdBy',
|
||||||
|
width: 80,
|
||||||
|
sorter: (a: Data, b: Data): number => {
|
||||||
|
const prev = new Date(a.createdBy).getTime();
|
||||||
|
const next = new Date(b.createdBy).getTime();
|
||||||
|
|
||||||
|
return prev - next;
|
||||||
|
},
|
||||||
|
render: Createdby,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Last Updated Time',
|
||||||
|
width: 90,
|
||||||
|
dataIndex: 'lastUpdatedTime',
|
||||||
|
sorter: (a: Data, b: Data): number => {
|
||||||
|
const prev = new Date(a.lastUpdatedTime).getTime();
|
||||||
|
const next = new Date(b.lastUpdatedTime).getTime();
|
||||||
|
|
||||||
|
return prev - next;
|
||||||
|
},
|
||||||
|
render: DateComponent,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
columns.push({
|
columns.push({
|
||||||
@ -199,7 +202,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
setUploadedGrafana(uploadedGrafana);
|
setUploadedGrafana(uploadedGrafana);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMenuItems = useCallback(() => {
|
const getMenuItems = useMemo(() => {
|
||||||
const menuItems: ItemType[] = [];
|
const menuItems: ItemType[] = [];
|
||||||
if (createNewDashboard) {
|
if (createNewDashboard) {
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
@ -227,7 +230,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
|
|
||||||
const menu: MenuProps = useMemo(
|
const menu: MenuProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
items: getMenuItems(),
|
items: getMenuItems,
|
||||||
}),
|
}),
|
||||||
[getMenuItems],
|
[getMenuItems],
|
||||||
);
|
);
|
||||||
@ -245,7 +248,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{newDashboard && (
|
{newDashboard && (
|
||||||
<Dropdown trigger={['click']} menu={menu}>
|
<Dropdown disabled={loading} trigger={['click']} menu={menu}>
|
||||||
<NewDashboardButton
|
<NewDashboardButton
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -260,11 +263,12 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
</Row>
|
</Row>
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
getText,
|
|
||||||
newDashboard,
|
newDashboard,
|
||||||
newDashboardState.error,
|
loading,
|
||||||
newDashboardState.loading,
|
|
||||||
menu,
|
menu,
|
||||||
|
newDashboardState.loading,
|
||||||
|
newDashboardState.error,
|
||||||
|
getText,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -34,10 +34,12 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
|
|||||||
CLICKHOUSE: uuid(),
|
CLICKHOUSE: uuid(),
|
||||||
PROM: uuid(),
|
PROM: uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { dashboards, isLoadingQueryResult } = useSelector<
|
const { dashboards, isLoadingQueryResult } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
DashboardReducer
|
DashboardReducer
|
||||||
>((state) => state.dashboards);
|
>((state) => state.dashboards);
|
||||||
|
|
||||||
const [selectedDashboards] = dashboards;
|
const [selectedDashboards] = dashboards;
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { widgets } = selectedDashboards.data;
|
const { widgets } = selectedDashboards.data;
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { Button, Modal, Typography } from 'antd';
|
import { LockFilled } from '@ant-design/icons';
|
||||||
|
import { Button, Modal, Tooltip, Typography } from 'antd';
|
||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||||
|
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
|
||||||
@ -22,6 +26,7 @@ import { AppState } from 'store/reducers';
|
|||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { FLUSH_DASHBOARD } from 'types/actions/dashboard';
|
import { FLUSH_DASHBOARD } from 'types/actions/dashboard';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
import DashboardReducer from 'types/reducer/dashboards';
|
import DashboardReducer from 'types/reducer/dashboards';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
@ -51,6 +56,10 @@ function NewWidget({
|
|||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
|
||||||
const [selectedDashboard] = dashboards;
|
const [selectedDashboard] = dashboards;
|
||||||
|
|
||||||
const { widgets } = selectedDashboard.data;
|
const { widgets } = selectedDashboard.data;
|
||||||
@ -99,22 +108,34 @@ function NewWidget({
|
|||||||
enum: selectedWidget?.timePreferance || 'GLOBAL_TIME',
|
enum: selectedWidget?.timePreferance || 'GLOBAL_TIME',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const onClickSaveHandler = useCallback(() => {
|
const onClickSaveHandler = useCallback(() => {
|
||||||
// update the global state
|
// update the global state
|
||||||
saveSettingOfPanel({
|
featureResponse
|
||||||
uuid: selectedDashboard.uuid,
|
.refetch()
|
||||||
description,
|
.then(() => {
|
||||||
isStacked: stacked,
|
saveSettingOfPanel({
|
||||||
nullZeroValues: selectedNullZeroValue,
|
uuid: selectedDashboard.uuid,
|
||||||
opacity,
|
description,
|
||||||
timePreferance: selectedTime.enum,
|
isStacked: stacked,
|
||||||
title,
|
nullZeroValues: selectedNullZeroValue,
|
||||||
yAxisUnit,
|
opacity,
|
||||||
widgetId: query.get('widgetId') || '',
|
timePreferance: selectedTime.enum,
|
||||||
dashboardId,
|
title,
|
||||||
graphType,
|
yAxisUnit,
|
||||||
});
|
widgetId: query.get('widgetId') || '',
|
||||||
|
dashboardId,
|
||||||
|
graphType,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Something went wrong',
|
||||||
|
});
|
||||||
|
});
|
||||||
}, [
|
}, [
|
||||||
|
featureResponse,
|
||||||
saveSettingOfPanel,
|
saveSettingOfPanel,
|
||||||
selectedDashboard.uuid,
|
selectedDashboard.uuid,
|
||||||
description,
|
description,
|
||||||
@ -127,6 +148,7 @@ function NewWidget({
|
|||||||
query,
|
query,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
graphType,
|
graphType,
|
||||||
|
notifications,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const onClickDiscardHandler = useCallback(() => {
|
const onClickDiscardHandler = useCallback(() => {
|
||||||
@ -167,13 +189,39 @@ function NewWidget({
|
|||||||
getQueryResult();
|
getQueryResult();
|
||||||
}, [getQueryResult]);
|
}, [getQueryResult]);
|
||||||
|
|
||||||
|
const onSaveDashboard = useCallback((): void => {
|
||||||
|
setSaveModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isQueryBuilderActive = useIsFeatureDisabled(
|
||||||
|
FeatureKeys.QUERY_BUILDER_PANELS,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<Button type="primary" onClick={(): void => setSaveModal(true)}>
|
{isQueryBuilderActive && (
|
||||||
Save
|
<Tooltip title={MESSAGE.PANEL}>
|
||||||
</Button>
|
<Button
|
||||||
{/* <Button onClick={onClickApplyHandler}>Apply</Button> */}
|
icon={<LockFilled />}
|
||||||
|
type="primary"
|
||||||
|
disabled={isQueryBuilderActive}
|
||||||
|
onClick={onSaveDashboard}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isQueryBuilderActive && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
disabled={isQueryBuilderActive}
|
||||||
|
onClick={onSaveDashboard}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button onClick={onClickDiscardHandler}>Discard</Button>
|
<Button onClick={onClickDiscardHandler}>Discard</Button>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ import { PlusOutlined } from '@ant-design/icons';
|
|||||||
import { Button, Form, Input, Modal, Typography } from 'antd';
|
import { Button, Form, Input, Modal, Typography } from 'antd';
|
||||||
import { useForm } from 'antd/es/form/Form';
|
import { useForm } from 'antd/es/form/Form';
|
||||||
import createDomainApi from 'api/SAML/postDomain';
|
import createDomainApi from 'api/SAML/postDomain';
|
||||||
import { FeatureKeys } from 'constants/featureKeys';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import useFeatureFlag from 'hooks/useFeatureFlag';
|
import useFeatureFlag from 'hooks/useFeatureFlag/useFeatureFlag';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -18,7 +18,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
|||||||
const { t } = useTranslation(['common', 'organizationsettings']);
|
const { t } = useTranslation(['common', 'organizationsettings']);
|
||||||
const [isAddDomains, setIsDomain] = useState(false);
|
const [isAddDomains, setIsDomain] = useState(false);
|
||||||
const [form] = useForm<FormProps>();
|
const [form] = useForm<FormProps>();
|
||||||
const SSOFlag = useFeatureFlag(FeatureKeys.SSO);
|
const isSsoFlagEnabled = useFeatureFlag(FeatureKeys.SSO);
|
||||||
|
|
||||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
|||||||
ns: 'organizationsettings',
|
ns: 'organizationsettings',
|
||||||
})}
|
})}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
{SSOFlag && (
|
{isSsoFlagEnabled && (
|
||||||
<Button
|
<Button
|
||||||
onClick={(): void => setIsDomain(true)}
|
onClick={(): void => setIsDomain(true)}
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -7,8 +7,8 @@ import updateDomain from 'api/SAML/updateDomain';
|
|||||||
import { ResizeTable } from 'components/ResizeTable';
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||||
import { FeatureKeys } from 'constants/featureKeys';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import useFeatureFlag from 'hooks/useFeatureFlag';
|
import useFeatureFlag from 'hooks/useFeatureFlag/useFeatureFlag';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Divider, Space } from 'antd';
|
import { Divider, Space } from 'antd';
|
||||||
import { FeatureKeys } from 'constants/featureKeys';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import useFeatureFlag from 'hooks/useFeatureFlag';
|
import { useIsFeatureDisabled } from 'hooks/useFeatureFlag';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -14,8 +14,11 @@ import PendingInvitesContainer from './PendingInvitesContainer';
|
|||||||
function OrganizationSettings(): JSX.Element {
|
function OrganizationSettings(): JSX.Element {
|
||||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const sso = useFeatureFlag(FeatureKeys.SSO);
|
const isNotSSO = useIsFeatureDisabled(FeatureKeys.SSO);
|
||||||
const noUpsell = useFeatureFlag(FeatureKeys.DISABLE_UPSELL);
|
|
||||||
|
const isNoUpSell = useIsFeatureDisabled(FeatureKeys.DISABLE_UPSELL);
|
||||||
|
|
||||||
|
const isAuthDomain = !isNoUpSell || (isNoUpSell && !isNotSSO);
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -38,7 +41,7 @@ function OrganizationSettings(): JSX.Element {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<Members />
|
<Members />
|
||||||
<Divider />
|
<Divider />
|
||||||
{(!noUpsell || (noUpsell && sso)) && <AuthDomains />}
|
{isAuthDomain && <AuthDomains />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,21 +16,6 @@ import { useSetCurrentKeyAndOperator } from './useSetCurrentKeyAndOperator';
|
|||||||
import { useTag } from './useTag';
|
import { useTag } from './useTag';
|
||||||
import { useTagValidation } from './useTagValidation';
|
import { useTagValidation } from './useTagValidation';
|
||||||
|
|
||||||
interface IAutoComplete {
|
|
||||||
updateTag: (value: string) => void;
|
|
||||||
handleSearch: (value: string) => void;
|
|
||||||
handleClearTag: (value: string) => void;
|
|
||||||
handleSelect: (value: string) => void;
|
|
||||||
handleKeyDown: (event: React.KeyboardEvent) => void;
|
|
||||||
options: Option[];
|
|
||||||
tags: string[];
|
|
||||||
searchValue: string;
|
|
||||||
isMulti: boolean;
|
|
||||||
isFetching: boolean;
|
|
||||||
setSearchKey: (value: string) => void;
|
|
||||||
searchKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
const [searchKey, setSearchKey] = useState<string>('');
|
const [searchKey, setSearchKey] = useState<string>('');
|
||||||
@ -134,3 +119,18 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => {
|
|||||||
searchKey,
|
searchKey,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface IAutoComplete {
|
||||||
|
updateTag: (value: string) => void;
|
||||||
|
handleSearch: (value: string) => void;
|
||||||
|
handleClearTag: (value: string) => void;
|
||||||
|
handleSelect: (value: string) => void;
|
||||||
|
handleKeyDown: (event: React.KeyboardEvent) => void;
|
||||||
|
options: Option[];
|
||||||
|
tags: string[];
|
||||||
|
searchValue: string;
|
||||||
|
isMulti: boolean;
|
||||||
|
isFetching: boolean;
|
||||||
|
setSearchKey: (value: string) => void;
|
||||||
|
searchKey: string;
|
||||||
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
import _get from 'lodash-es/get';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppReducer from 'types/reducer/app';
|
|
||||||
|
|
||||||
const useFeatureFlag = (flagKey: string): boolean => {
|
|
||||||
const { featureFlags } = useSelector<AppState, AppReducer>(
|
|
||||||
(state) => state.app,
|
|
||||||
);
|
|
||||||
return _get(featureFlags, flagKey, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useFeatureFlag;
|
|
9
frontend/src/hooks/useFeatureFlag/constant.ts
Normal file
9
frontend/src/hooks/useFeatureFlag/constant.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export const MESSAGE = {
|
||||||
|
PANEL:
|
||||||
|
'You have exceeded the number of logs and traces based panels using query builder that are allowed in the community edition.',
|
||||||
|
ALERT:
|
||||||
|
'You have exceeded the number of alerts that are allowed in the community edition.',
|
||||||
|
WIDGET: 'You have reached limit of {{widget}} free widgets.',
|
||||||
|
CREATE_DASHBOARD:
|
||||||
|
'You have reached limit of creating the query builder based dashboard panels.',
|
||||||
|
};
|
7
frontend/src/hooks/useFeatureFlag/index.ts
Normal file
7
frontend/src/hooks/useFeatureFlag/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { MESSAGE } from './constant';
|
||||||
|
import useFeatureFlag from './useFeatureFlag';
|
||||||
|
import useIsFeatureDisabled from './useIsFeatureDisabled';
|
||||||
|
|
||||||
|
export default useFeatureFlag;
|
||||||
|
|
||||||
|
export { MESSAGE, useIsFeatureDisabled };
|
29
frontend/src/hooks/useFeatureFlag/useFeatureFlag.ts
Normal file
29
frontend/src/hooks/useFeatureFlag/useFeatureFlag.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
const useFeatureFlag = (
|
||||||
|
flagKey: keyof typeof FeatureKeys,
|
||||||
|
): FlatArray<FeatureFlagPayload, 1> | undefined => {
|
||||||
|
const { featureResponse = [] } = useSelector<AppState, AppReducer>(
|
||||||
|
(state) => state.app,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (featureResponse === null) return undefined;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
const featureResponseData = featureResponse.data as FeatureFlagPayload;
|
||||||
|
|
||||||
|
const feature = featureResponseData?.find((flag) => flag.name === flagKey);
|
||||||
|
|
||||||
|
if (!feature) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return feature;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFeatureFlag;
|
11
frontend/src/hooks/useFeatureFlag/useIsFeatureDisabled.ts
Normal file
11
frontend/src/hooks/useFeatureFlag/useIsFeatureDisabled.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
|
|
||||||
|
import useFeatureFlag from './useFeatureFlag';
|
||||||
|
|
||||||
|
const useIsFeatureDisabled = (props: keyof typeof FeatureKeys): boolean => {
|
||||||
|
const feature = useFeatureFlag(props);
|
||||||
|
|
||||||
|
return !feature?.active ?? false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useIsFeatureDisabled;
|
3
frontend/src/hooks/useLicense/constant.ts
Normal file
3
frontend/src/hooks/useLicense/constant.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const LICENSE_PLAN_KEY = {
|
||||||
|
ENTERPRISE_PLAN: 'ENTERPRISE_PLAN',
|
||||||
|
};
|
6
frontend/src/hooks/useLicense/index.ts
Normal file
6
frontend/src/hooks/useLicense/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { LICENSE_PLAN_KEY } from './constant';
|
||||||
|
import useLicense from './useLicense';
|
||||||
|
|
||||||
|
export default useLicense;
|
||||||
|
|
||||||
|
export { LICENSE_PLAN_KEY };
|
25
frontend/src/hooks/useLicense/useLicense.tsx
Normal file
25
frontend/src/hooks/useLicense/useLicense.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import getAll from 'api/licenses/getAll';
|
||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps } from 'types/api/licenses/getAll';
|
||||||
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
const useLicense = (): UseLicense => {
|
||||||
|
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryFn: getAll,
|
||||||
|
queryKey: [REACT_QUERY_KEY.GET_ALL_LICENCES, user?.email],
|
||||||
|
enabled: !!user?.email,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseLicense = UseQueryResult<
|
||||||
|
SuccessResponse<PayloadProps> | ErrorResponse,
|
||||||
|
unknown
|
||||||
|
>;
|
||||||
|
|
||||||
|
export default useLicense;
|
@ -9,7 +9,7 @@ import {
|
|||||||
UPDATE_CONFIGS,
|
UPDATE_CONFIGS,
|
||||||
UPDATE_CURRENT_ERROR,
|
UPDATE_CURRENT_ERROR,
|
||||||
UPDATE_CURRENT_VERSION,
|
UPDATE_CURRENT_VERSION,
|
||||||
UPDATE_FEATURE_FLAGS,
|
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||||
UPDATE_LATEST_VERSION,
|
UPDATE_LATEST_VERSION,
|
||||||
UPDATE_LATEST_VERSION_ERROR,
|
UPDATE_LATEST_VERSION_ERROR,
|
||||||
UPDATE_ORG,
|
UPDATE_ORG,
|
||||||
@ -47,7 +47,10 @@ const InitialValue: InitialValueTypes = {
|
|||||||
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
|
||||||
currentVersion: '',
|
currentVersion: '',
|
||||||
latestVersion: '',
|
latestVersion: '',
|
||||||
featureFlags: {},
|
featureResponse: {
|
||||||
|
data: null,
|
||||||
|
refetch: Promise.resolve,
|
||||||
|
},
|
||||||
isCurrentVersionError: false,
|
isCurrentVersionError: false,
|
||||||
isLatestVersionError: false,
|
isLatestVersionError: false,
|
||||||
user: getInitialUser(),
|
user: getInitialUser(),
|
||||||
@ -80,10 +83,13 @@ const appReducer = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_FEATURE_FLAGS: {
|
case UPDATE_FEATURE_FLAG_RESPONSE: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
featureFlags: { ...action.payload },
|
featureResponse: {
|
||||||
|
data: action.payload.featureFlag,
|
||||||
|
refetch: action.payload.refetch,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { QueryObserverBaseResult } from 'react-query';
|
||||||
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
||||||
import {
|
import {
|
||||||
Organization,
|
Organization,
|
||||||
@ -22,9 +23,9 @@ export const UPDATE_USER_ORG_ROLE = 'UPDATE_USER_ORG_ROLE';
|
|||||||
export const UPDATE_USER = 'UPDATE_USER';
|
export const UPDATE_USER = 'UPDATE_USER';
|
||||||
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
|
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
|
||||||
export const UPDATE_ORG = 'UPDATE_ORG';
|
export const UPDATE_ORG = 'UPDATE_ORG';
|
||||||
export const UPDATE_FEATURE_FLAGS = 'UPDATE_FEATURE_FLAGS';
|
|
||||||
export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
|
export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
|
||||||
export const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG';
|
export const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG';
|
||||||
|
export const UPDATE_FEATURE_FLAG_RESPONSE = 'UPDATE_FEATURE_FLAG_RESPONSE';
|
||||||
|
|
||||||
export interface LoggedInUser {
|
export interface LoggedInUser {
|
||||||
type: typeof LOGGED_IN;
|
type: typeof LOGGED_IN;
|
||||||
@ -38,10 +39,6 @@ export interface SideBarCollapse {
|
|||||||
payload: boolean;
|
payload: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateFeatureFlags {
|
|
||||||
type: typeof UPDATE_FEATURE_FLAGS;
|
|
||||||
payload: null | FeatureFlagPayload;
|
|
||||||
}
|
|
||||||
export interface UpdateAppVersion {
|
export interface UpdateAppVersion {
|
||||||
type: typeof UPDATE_CURRENT_VERSION;
|
type: typeof UPDATE_CURRENT_VERSION;
|
||||||
payload: {
|
payload: {
|
||||||
@ -130,6 +127,14 @@ export interface UpdateConfigs {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateFeatureFlag {
|
||||||
|
type: typeof UPDATE_FEATURE_FLAG_RESPONSE;
|
||||||
|
payload: {
|
||||||
|
featureFlag: FeatureFlagPayload;
|
||||||
|
refetch: QueryObserverBaseResult['refetch'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type AppAction =
|
export type AppAction =
|
||||||
| LoggedInUser
|
| LoggedInUser
|
||||||
| SideBarCollapse
|
| SideBarCollapse
|
||||||
@ -142,6 +147,6 @@ export type AppAction =
|
|||||||
| UpdateUser
|
| UpdateUser
|
||||||
| UpdateOrgName
|
| UpdateOrgName
|
||||||
| UpdateOrg
|
| UpdateOrg
|
||||||
| UpdateFeatureFlags
|
|
||||||
| UpdateConfigs
|
| UpdateConfigs
|
||||||
| UpdateUserFlag;
|
| UpdateUserFlag
|
||||||
|
| UpdateFeatureFlag;
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
export interface PayloadProps {
|
|
||||||
[key: string]: boolean;
|
|
||||||
}
|
|
@ -1,3 +1,19 @@
|
|||||||
export interface PayloadProps {
|
export type FeaturesFlag =
|
||||||
[key: string]: boolean;
|
| 'DurationSort'
|
||||||
|
| 'TimestampSort'
|
||||||
|
| 'SMART_TRACE_DETAIL'
|
||||||
|
| 'CUSTOM_METRICS_FUNCTION'
|
||||||
|
| 'QUERY_BUILDER_PANELS'
|
||||||
|
| 'QUERY_BUILDER_ALERTS'
|
||||||
|
| 'DISABLE_UPSELL'
|
||||||
|
| 'SSO';
|
||||||
|
|
||||||
|
interface FeatureFlagProps {
|
||||||
|
name: FeaturesFlag;
|
||||||
|
active: boolean;
|
||||||
|
usage: number;
|
||||||
|
usage_limit: number;
|
||||||
|
route: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = FeatureFlagProps[];
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { QueryObserverBaseResult } from 'react-query';
|
||||||
import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
||||||
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
||||||
import { PayloadProps as OrgPayload } from 'types/api/user/getOrganization';
|
import { PayloadProps as OrgPayload } from 'types/api/user/getOrganization';
|
||||||
@ -26,9 +27,12 @@ export default interface AppReducer {
|
|||||||
isUserFetchingError: boolean;
|
isUserFetchingError: boolean;
|
||||||
role: ROLES | null;
|
role: ROLES | null;
|
||||||
org: OrgPayload | null;
|
org: OrgPayload | null;
|
||||||
featureFlags: null | FeatureFlagPayload;
|
|
||||||
configs: ConfigPayload;
|
configs: ConfigPayload;
|
||||||
userFlags: null | UserFlags;
|
userFlags: null | UserFlags;
|
||||||
ee: 'Y' | 'N';
|
ee: 'Y' | 'N';
|
||||||
setupCompleted: boolean;
|
setupCompleted: boolean;
|
||||||
|
featureResponse: {
|
||||||
|
data: FeatureFlagPayload | null;
|
||||||
|
refetch: QueryObserverBaseResult['refetch'];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user