* Select Data Source
-
{supportedDataSources?.map((dataSource) => (
- {selectedModule?.id === useCases.APM.id && (
-
);
}
diff --git a/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx b/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx
index fd77b14fe2..73ceb80037 100644
--- a/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx
+++ b/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx
@@ -1,8 +1,13 @@
-import { Card, Typography } from 'antd';
+import { LoadingOutlined } from '@ant-design/icons';
+import { Button, Card, Form, Input, Space, Typography } from 'antd';
+import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
-import { Server } from 'lucide-react';
+import { useNotifications } from 'hooks/useNotifications';
+import { Check, Server } from 'lucide-react';
+import { useState } from 'react';
+import { useTranslation } from 'react-i18next';
interface SupportedEnvironmentsProps {
name: string;
@@ -37,16 +42,78 @@ const supportedEnvironments: SupportedEnvironmentsProps[] = [
];
export default function EnvironmentDetails(): JSX.Element {
+ const [form] = Form.useForm();
+ const { t } = useTranslation(['common']);
+
const {
selectedEnvironment,
updateSelectedEnvironment,
selectedModule,
+ selectedDataSource,
+ selectedFramework,
errorDetails,
updateErrorDetails,
} = useOnboardingContext();
+ const requestedEnvironmentName = Form.useWatch(
+ 'requestedEnvironmentName',
+ form,
+ );
+
+ const { notifications } = useNotifications();
+
+ const [
+ isSubmittingRequestForEnvironment,
+ setIsSubmittingRequestForEnvironment,
+ ] = useState(false);
+
+ const handleRequestedEnvironmentSubmit = async (): Promise => {
+ try {
+ setIsSubmittingRequestForEnvironment(true);
+ const response = await logEvent('Onboarding V2: Environment Requested', {
+ module: selectedModule?.id,
+ dataSource: selectedDataSource?.id,
+ framework: selectedFramework,
+ environment: requestedEnvironmentName,
+ });
+
+ if (response.statusCode === 200) {
+ notifications.success({
+ message: 'Environment Request Submitted',
+ });
+
+ form.setFieldValue('requestedEnvironmentName', '');
+
+ setIsSubmittingRequestForEnvironment(false);
+ } else {
+ notifications.error({
+ message:
+ response.error ||
+ t('something_went_wrong', {
+ ns: 'common',
+ }),
+ });
+
+ setIsSubmittingRequestForEnvironment(false);
+ }
+ } catch (error) {
+ notifications.error({
+ message: t('something_went_wrong', {
+ ns: 'common',
+ }),
+ });
+
+ setIsSubmittingRequestForEnvironment(false);
+ }
+ };
+
return (
- <>
+
+
+
+ Cannot find what you’re looking for? Request a data source
+
+
+
+
+
+
+
+
+ ) : (
+
+ )
+ }
+ type="primary"
+ onClick={handleRequestedEnvironmentSubmit}
+ disabled={
+ isSubmittingRequestForEnvironment ||
+ !requestedEnvironmentName ||
+ requestedEnvironmentName?.trim().length === 0
+ }
+ >
+ Submit
+
+
+
+
+
{errorDetails && (
{errorDetails}
)}
- >
+
);
}
diff --git a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx
index 272d2b5083..662daedeaa 100644
--- a/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx
+++ b/frontend/src/container/OnboardingContainer/common/ModuleStepsContainer/ModuleStepsContainer.tsx
@@ -16,7 +16,7 @@ import { DataSourceType } from 'container/OnboardingContainer/Steps/DataSource/D
import { hasFrameworks } from 'container/OnboardingContainer/utils/dataSourceUtils';
import useAnalytics from 'hooks/analytics/useAnalytics';
import history from 'lib/history';
-import { isEmpty } from 'lodash-es';
+import { isEmpty, isNull } from 'lodash-es';
import { useState } from 'react';
import { useOnboardingContext } from '../../context/OnboardingContext';
@@ -91,7 +91,10 @@ export default function ModuleStepsContainer({
name: selectedDataSourceName = '',
} = selectedDataSource as DataSourceType;
- if (step.id === environmentDetailsStep && selectedEnvironment === '') {
+ if (
+ step.id === environmentDetailsStep &&
+ (selectedEnvironment === '' || isNull(selectedEnvironment))
+ ) {
updateErrorDetails('Please select environment');
return false;
}
diff --git a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
index eddd599a69..485a33382c 100644
--- a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
+++ b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
@@ -403,11 +403,38 @@ import APM_javascript_reactjs_macOsARM64_quickStart_runApplication from '../Modu
import APM_javascript_reactjs_macOsARM64_recommendedSteps_setupOtelCollector from '../Modules/APM/Javascript/md-docs/ReactJS/MacOsARM64/Recommended/reactjs-macosarm64-recommended-installOtelCollector.md';
import APM_javascript_reactjs_macOsARM64_recommendedSteps_instrumentApplication from '../Modules/APM/Javascript/md-docs/ReactJS/MacOsARM64/Recommended/reactjs-macosarm64-recommended-instrumentApplication.md';
import APM_javascript_reactjs_macOsARM64_recommendedSteps_runApplication from '../Modules/APM/Javascript/md-docs/ReactJS/MacOsARM64/Recommended/reactjs-macosarm64-recommended-runApplication.md';
-import APM_python_django_docker_quickStart_instrumentApplication from '../Modules/APM/Python/md-docs/Django/Docker/QuickStart/django-docker-quickStart-instrumentApplication.md';
-import APM_python_django_docker_quickStart_runApplication from '../Modules/APM/Python/md-docs/Django/Docker/QuickStart/django-docker-quickStart-runApplication.md';
-import APM_python_django_docker_recommendedSteps_setupOtelCollector from '../Modules/APM/Python/md-docs/Django/Docker/Recommended/django-docker-recommended-installOtelCollector.md';
-import APM_python_django_docker_recommendedSteps_instrumentApplication from '../Modules/APM/Python/md-docs/Django/Docker/Recommended/django-docker-recommended-instrumentApplication.md';
-import APM_python_django_docker_recommendedSteps_runApplication from '../Modules/APM/Python/md-docs/Django/Docker/Recommended/django-docker-recommended-runApplication.md';
+// PHP-Kubernetes
+import APM_php_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/Php/md-docs/Kubernetes/php-kubernetes-installOtelCollector.md';
+import APM_php_kubernetes_recommendedSteps_instrumentApplication from '../Modules/APM/Php/md-docs/Kubernetes/php-kubernetes-instrumentApplication.md';
+import APM_php_kubernetes_recommendedSteps_runApplication from '../Modules/APM/Php/md-docs/Kubernetes/php-kubernetes-runApplication.md';
+// PHP-LinuxAMD64-quickstart
+import APM_php_linuxAMD64_quickStart_instrumentApplication from '../Modules/APM/Php/md-docs/LinuxAMD64/QuickStart/php-linuxamd64-quickStart-instrumentApplication.md';
+import APM_php_linuxAMD64_quickStart_runApplication from '../Modules/APM/Php/md-docs/LinuxAMD64/QuickStart/php-linuxamd64-quickStart-runApplication.md';
+// PHP-LinuxAMD64-recommended
+import APM_php_linuxAMD64_recommendedSteps_setupOtelCollector from '../Modules/APM/Php/md-docs/LinuxAMD64/Recommended/php-linuxamd64-recommended-installOtelCollector.md';
+import APM_php_linuxAMD64_recommendedSteps_instrumentApplication from '../Modules/APM/Php/md-docs/LinuxAMD64/Recommended/php-linuxamd64-recommended-instrumentApplication.md';
+import APM_php_linuxAMD64_recommendedSteps_runApplication from '../Modules/APM/Php/md-docs/LinuxAMD64/Recommended/php-linuxamd64-recommended-runApplication.md';
+// PHP-LinuxARM64-quickstart
+import APM_php_linuxARM64_quickStart_instrumentApplication from '../Modules/APM/Php/md-docs/LinuxARM64/QuickStart/php-linuxarm64-quickStart-instrumentApplication.md';
+import APM_php_linuxARM64_quickStart_runApplication from '../Modules/APM/Php/md-docs/LinuxARM64/QuickStart/php-linuxarm64-quickStart-runApplication.md';
+// PHP-LinuxARM64-recommended
+import APM_php_linuxARM64_recommendedSteps_setupOtelCollector from '../Modules/APM/Php/md-docs/LinuxARM64/Recommended/php-linuxarm64-recommended-installOtelCollector.md';
+import APM_php_linuxARM64_recommendedSteps_instrumentApplication from '../Modules/APM/Php/md-docs/LinuxARM64/Recommended/php-linuxarm64-recommended-instrumentApplication.md';
+import APM_php_linuxARM64_recommendedSteps_runApplication from '../Modules/APM/Php/md-docs/LinuxARM64/Recommended/php-linuxarm64-recommended-runApplication.md';
+// PHP-MacOsAMD64-quickstart
+import APM_php_macOsAMD64_quickStart_instrumentApplication from '../Modules/APM/Php/md-docs/MacOsAMD64/QuickStart/php-macosamd64-quickStart-instrumentApplication.md';
+import APM_php_macOsAMD64_quickStart_runApplication from '../Modules/APM/Php/md-docs/MacOsAMD64/QuickStart/php-macosamd64-quickStart-runApplication.md';
+// PHP-MacOsAMD64-recommended
+import APM_php_macOsAMD64_recommendedSteps_setupOtelCollector from '../Modules/APM/Php/md-docs/MacOsAMD64/Recommended/php-macosamd64-recommended-installOtelCollector.md';
+import APM_php_macOsAMD64_recommendedSteps_instrumentApplication from '../Modules/APM/Php/md-docs/MacOsAMD64/Recommended/php-macosamd64-recommended-instrumentApplication.md';
+import APM_php_macOsAMD64_recommendedSteps_runApplication from '../Modules/APM/Php/md-docs/MacOsAMD64/Recommended/php-macosamd64-recommended-runApplication.md';
+// PHP-MacOsARM64-quickstart
+import APM_php_macOsARM64_quickStart_instrumentApplication from '../Modules/APM/Php/md-docs/MacOsARM64/QuickStart/php-macosarm64-quickStart-instrumentApplication.md';
+import APM_php_macOsARM64_quickStart_runApplication from '../Modules/APM/Php/md-docs/MacOsARM64/QuickStart/php-macosarm64-quickStart-runApplication.md';
+// PHP-MacOsARM64-recommended
+import APM_php_macOsARM64_recommendedSteps_setupOtelCollector from '../Modules/APM/Php/md-docs/MacOsARM64/Recommended/php-macosarm64-recommended-installOtelCollector.md';
+import APM_php_macOsARM64_recommendedSteps_instrumentApplication from '../Modules/APM/Php/md-docs/MacOsARM64/Recommended/php-macosarm64-recommended-instrumentApplication.md';
+import APM_php_macOsARM64_recommendedSteps_runApplication from '../Modules/APM/Php/md-docs/MacOsARM64/Recommended/php-macosarm64-recommended-runApplication.md';
/// ////// Javascript Done
/// ///// Python Start
// Django
@@ -580,7 +607,6 @@ import APM_python_other_macOsARM64_recommendedSteps_setupOtelCollector from '../
import APM_python_other_macOsARM64_recommendedSteps_instrumentApplication from '../Modules/APM/Python/md-docs/Others/MacOsARM64/Recommended/others-macosarm64-recommended-instrumentApplication.md';
import APM_python_other_macOsARM64_recommendedSteps_runApplication from '../Modules/APM/Python/md-docs/Others/MacOsARM64/Recommended/others-macosarm64-recommended-runApplication.md';
// ----------------------------------------------------------------------------
-/// ////// Go Done
/// ///// ROR Start
// ROR-Kubernetes
import APM_rails_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/RubyOnRails/md-docs/Kubernetes/ror-kubernetes-installOtelCollector.md';
@@ -908,14 +934,6 @@ export const ApmDocFilePaths = {
APM_python_django_macOsARM64_quickStart_instrumentApplication,
APM_python_django_macOsARM64_quickStart_runApplication,
- // Django Docker
- APM_python_django_docker_quickStart_instrumentApplication,
- APM_python_django_docker_quickStart_runApplication,
-
- APM_python_django_docker_recommendedSteps_setupOtelCollector,
- APM_python_django_docker_recommendedSteps_instrumentApplication,
- APM_python_django_docker_recommendedSteps_runApplication,
-
// ------------------------------------------------------------------------------------------------
// Flask
@@ -1559,4 +1577,36 @@ export const ApmDocFilePaths = {
APM_swift_macOsARM64_recommendedSteps_setupOtelCollector,
APM_swift_macOsARM64_recommendedSteps_instrumentApplication,
APM_swift_macOsARM64_recommendedSteps_runApplication,
+
+ APM_php_kubernetes_recommendedSteps_setupOtelCollector,
+ APM_php_kubernetes_recommendedSteps_instrumentApplication,
+ APM_php_kubernetes_recommendedSteps_runApplication,
+
+ APM_php_linuxAMD64_quickStart_instrumentApplication,
+ APM_php_linuxAMD64_quickStart_runApplication,
+
+ APM_php_linuxAMD64_recommendedSteps_setupOtelCollector,
+ APM_php_linuxAMD64_recommendedSteps_instrumentApplication,
+ APM_php_linuxAMD64_recommendedSteps_runApplication,
+
+ APM_php_linuxARM64_quickStart_instrumentApplication,
+ APM_php_linuxARM64_quickStart_runApplication,
+
+ APM_php_linuxARM64_recommendedSteps_setupOtelCollector,
+ APM_php_linuxARM64_recommendedSteps_instrumentApplication,
+ APM_php_linuxARM64_recommendedSteps_runApplication,
+
+ APM_php_macOsAMD64_quickStart_instrumentApplication,
+ APM_php_macOsAMD64_quickStart_runApplication,
+
+ APM_php_macOsAMD64_recommendedSteps_setupOtelCollector,
+ APM_php_macOsAMD64_recommendedSteps_instrumentApplication,
+ APM_php_macOsAMD64_recommendedSteps_runApplication,
+
+ APM_php_macOsARM64_quickStart_instrumentApplication,
+ APM_php_macOsARM64_quickStart_runApplication,
+
+ APM_php_macOsARM64_recommendedSteps_setupOtelCollector,
+ APM_php_macOsARM64_recommendedSteps_instrumentApplication,
+ APM_php_macOsARM64_recommendedSteps_runApplication,
};
diff --git a/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts b/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts
index 77f1210858..517cc38171 100644
--- a/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts
+++ b/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts
@@ -132,6 +132,11 @@ const supportedLanguages = [
id: 'swift',
imgURL: `/Logos/swift.png`,
},
+ {
+ name: 'php',
+ id: 'php',
+ imgURL: `/Logos/php.png`,
+ },
];
export const defaultLogsType = {
@@ -293,7 +298,8 @@ export const getSupportedFrameworks = ({
(moduleID === ModulesMap.APM && dataSourceName === '.NET') ||
(moduleID === ModulesMap.APM && dataSourceName === 'rust') ||
(moduleID === ModulesMap.APM && dataSourceName === 'elixir') ||
- (moduleID === ModulesMap.APM && dataSourceName === 'swift')
+ (moduleID === ModulesMap.APM && dataSourceName === 'swift') ||
+ (moduleID === ModulesMap.APM && dataSourceName === 'php')
) {
return [];
}
@@ -322,7 +328,8 @@ export const hasFrameworks = ({
(moduleID === ModulesMap.APM && dataSourceName === '.NET') ||
(moduleID === ModulesMap.APM && dataSourceName === 'rust') ||
(moduleID === ModulesMap.APM && dataSourceName === 'elixir') ||
- (moduleID === ModulesMap.APM && dataSourceName === 'swift')
+ (moduleID === ModulesMap.APM && dataSourceName === 'swift') ||
+ (moduleID === ModulesMap.APM && dataSourceName === 'php')
) {
return false;
}
diff --git a/frontend/src/container/OptionsMenu/constants.ts b/frontend/src/container/OptionsMenu/constants.ts
index b1e5463686..2db02f85b8 100644
--- a/frontend/src/container/OptionsMenu/constants.ts
+++ b/frontend/src/container/OptionsMenu/constants.ts
@@ -1,3 +1,5 @@
+import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
+
import { OptionsQuery } from './types';
export const URL_OPTIONS = 'options';
@@ -7,3 +9,46 @@ export const defaultOptionsQuery: OptionsQuery = {
maxLines: 2,
format: 'list',
};
+
+export const defaultTraceSelectedColumns = [
+ {
+ key: 'serviceName',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'serviceName--string--tag--true',
+ },
+ {
+ key: 'name',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'name--string--tag--true',
+ },
+ {
+ key: 'durationNano',
+ dataType: DataTypes.Float64,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'durationNano--float64--tag--true',
+ },
+ {
+ key: 'httpMethod',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'httpMethod--string--tag--true',
+ },
+ {
+ key: 'responseStatusCode',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'responseStatusCode--string--tag--true',
+ },
+];
diff --git a/frontend/src/container/OptionsMenu/useOptionsMenu.ts b/frontend/src/container/OptionsMenu/useOptionsMenu.ts
index be2ae00b37..97fbbbb006 100644
--- a/frontend/src/container/OptionsMenu/useOptionsMenu.ts
+++ b/frontend/src/container/OptionsMenu/useOptionsMenu.ts
@@ -16,7 +16,11 @@ import {
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
-import { defaultOptionsQuery, URL_OPTIONS } from './constants';
+import {
+ defaultOptionsQuery,
+ defaultTraceSelectedColumns,
+ URL_OPTIONS,
+} from './constants';
import { InitialOptions, OptionsMenuConfig, OptionsQuery } from './types';
import { getOptionsFromKeys } from './utils';
@@ -124,20 +128,29 @@ const useOptionsMenu = ({
{ queryKey: [debouncedSearchText, isFocused], enabled: isFocused },
);
- const searchedAttributeKeys = useMemo(
- () => searchedAttributesData?.payload?.attributeKeys || [],
- [searchedAttributesData?.payload?.attributeKeys],
- );
+ const searchedAttributeKeys = useMemo(() => {
+ if (searchedAttributesData?.payload?.attributeKeys?.length) {
+ return searchedAttributesData.payload.attributeKeys;
+ }
+ if (dataSource === DataSource.TRACES) {
+ return defaultTraceSelectedColumns;
+ }
+
+ return [];
+ }, [dataSource, searchedAttributesData?.payload?.attributeKeys]);
const initialOptionsQuery: OptionsQuery = useMemo(
() => ({
...defaultOptionsQuery,
...initialOptions,
+ // eslint-disable-next-line no-nested-ternary
selectColumns: initialOptions?.selectColumns
? initialSelectedColumns
+ : dataSource === DataSource.TRACES
+ ? defaultTraceSelectedColumns
: defaultOptionsQuery.selectColumns,
}),
- [initialOptions, initialSelectedColumns],
+ [dataSource, initialOptions, initialSelectedColumns],
);
const selectedColumnKeys = useMemo(
diff --git a/frontend/src/container/PanelWrapper/ListPanelWrapper.tsx b/frontend/src/container/PanelWrapper/ListPanelWrapper.tsx
new file mode 100644
index 0000000000..f43fc4e309
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/ListPanelWrapper.tsx
@@ -0,0 +1,37 @@
+import LogsPanelComponent from 'container/LogsPanelTable/LogsPanelComponent';
+import TracesTableComponent from 'container/TracesTableComponent/TracesTableComponent';
+import { DataSource } from 'types/common/queryBuilder';
+
+import { PanelWrapperProps } from './panelWrapper.types';
+
+function ListPanelWrapper({
+ widget,
+ queryResponse,
+ setRequestData,
+}: PanelWrapperProps): JSX.Element {
+ const dataSource = widget.query.builder?.queryData[0]?.dataSource;
+
+ if (!setRequestData) {
+ // eslint-disable-next-line react/jsx-no-useless-fragment
+ return <>>;
+ }
+
+ if (dataSource === DataSource.LOGS) {
+ return (
+
+ );
+ }
+ return (
+
+ );
+}
+
+export default ListPanelWrapper;
diff --git a/frontend/src/container/PanelWrapper/PanelWrapper.tsx b/frontend/src/container/PanelWrapper/PanelWrapper.tsx
new file mode 100644
index 0000000000..87090ac6c1
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/PanelWrapper.tsx
@@ -0,0 +1,40 @@
+import { FC } from 'react';
+
+import { PanelTypeVsPanelWrapper } from './constants';
+import { PanelWrapperProps } from './panelWrapper.types';
+
+function PanelWrapper({
+ widget,
+ queryResponse,
+ setRequestData,
+ isFullViewMode,
+ setGraphVisibility,
+ graphVisibility,
+ onToggleModelHandler,
+ onClickHandler,
+ onDragSelect,
+}: PanelWrapperProps): JSX.Element {
+ const Component = PanelTypeVsPanelWrapper[
+ widget.panelTypes
+ ] as FC
;
+
+ if (!Component) {
+ // eslint-disable-next-line react/jsx-no-useless-fragment
+ return <>>;
+ }
+ return (
+
+ );
+}
+
+export default PanelWrapper;
diff --git a/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx
new file mode 100644
index 0000000000..10a41493df
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/TablePanelWrapper.tsx
@@ -0,0 +1,24 @@
+import GridTableComponent from 'container/GridTableComponent';
+import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
+
+import { PanelWrapperProps } from './panelWrapper.types';
+
+function TablePanelWrapper({
+ widget,
+ queryResponse,
+}: PanelWrapperProps): JSX.Element {
+ const panelData =
+ queryResponse.data?.payload?.data.newResult.data.result || [];
+ const { thresholds } = widget;
+ return (
+
+ );
+}
+
+export default TablePanelWrapper;
diff --git a/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx b/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx
new file mode 100644
index 0000000000..256ff3f90b
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/UplotPanelWrapper.tsx
@@ -0,0 +1,141 @@
+import { ToggleGraphProps } from 'components/Graph/types';
+import Uplot from 'components/Uplot';
+import { PANEL_TYPES } from 'constants/queryBuilder';
+import GraphManager from 'container/GridCardLayout/GridCard/FullView/GraphManager';
+import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { useIsDarkMode } from 'hooks/useDarkMode';
+import { useResizeObserver } from 'hooks/useDimensions';
+import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
+import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
+import _noop from 'lodash-es/noop';
+import { useDashboard } from 'providers/Dashboard/Dashboard';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { getSortedSeriesData } from 'utils/getSortedSeriesData';
+import { getTimeRange } from 'utils/getTimeRange';
+
+import { PanelWrapperProps } from './panelWrapper.types';
+
+function UplotPanelWrapper({
+ queryResponse,
+ widget,
+ isFullViewMode,
+ setGraphVisibility,
+ graphVisibility,
+ onToggleModelHandler,
+ onClickHandler,
+ onDragSelect,
+}: PanelWrapperProps): JSX.Element {
+ const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
+ const isDarkMode = useIsDarkMode();
+ const lineChartRef = useRef();
+ const graphRef = useRef(null);
+ const [minTimeScale, setMinTimeScale] = useState();
+ const [maxTimeScale, setMaxTimeScale] = useState();
+ const { currentQuery } = useQueryBuilder();
+
+ useEffect(() => {
+ if (toScrollWidgetId === widget.id) {
+ graphRef.current?.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+ graphRef.current?.focus();
+ setToScrollWidgetId('');
+ }
+ }, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
+
+ useEffect((): void => {
+ const { startTime, endTime } = getTimeRange(queryResponse);
+
+ setMinTimeScale(startTime);
+ setMaxTimeScale(endTime);
+ }, [queryResponse]);
+
+ const containerDimensions = useResizeObserver(graphRef);
+
+ useEffect(() => {
+ const {
+ graphVisibilityStates: localStoredVisibilityState,
+ } = getLocalStorageGraphVisibilityState({
+ apiResponse: queryResponse.data?.payload.data.result || [],
+ name: widget.id,
+ });
+ if (setGraphVisibility) {
+ setGraphVisibility(localStoredVisibilityState);
+ }
+ }, [queryResponse.data?.payload.data.result, setGraphVisibility, widget.id]);
+
+ if (queryResponse.data && widget.panelTypes === PANEL_TYPES.BAR) {
+ const sortedSeriesData = getSortedSeriesData(
+ queryResponse.data?.payload.data.result,
+ );
+ // eslint-disable-next-line no-param-reassign
+ queryResponse.data.payload.data.result = sortedSeriesData;
+ }
+
+ const chartData = getUPlotChartData(
+ queryResponse?.data?.payload,
+ widget.fillSpans,
+ );
+
+ const options = useMemo(
+ () =>
+ getUPlotChartOptions({
+ id: widget?.id,
+ apiResponse: queryResponse.data?.payload,
+ dimensions: containerDimensions,
+ isDarkMode,
+ onDragSelect,
+ yAxisUnit: widget?.yAxisUnit,
+ onClickHandler: onClickHandler || _noop,
+ thresholds: widget.thresholds,
+ minTimeScale,
+ maxTimeScale,
+ softMax: widget.softMax === undefined ? null : widget.softMax,
+ softMin: widget.softMin === undefined ? null : widget.softMin,
+ graphsVisibilityStates: graphVisibility,
+ setGraphsVisibilityStates: setGraphVisibility,
+ panelType: widget.panelTypes,
+ currentQuery,
+ }),
+ [
+ widget?.id,
+ widget?.yAxisUnit,
+ widget.thresholds,
+ widget.softMax,
+ widget.softMin,
+ widget.panelTypes,
+ queryResponse.data?.payload,
+ containerDimensions,
+ isDarkMode,
+ onDragSelect,
+ onClickHandler,
+ minTimeScale,
+ maxTimeScale,
+ graphVisibility,
+ setGraphVisibility,
+ currentQuery,
+ ],
+ );
+
+ return (
+
+
+ {isFullViewMode && setGraphVisibility && (
+
+ )}
+
+ );
+}
+
+export default UplotPanelWrapper;
diff --git a/frontend/src/container/PanelWrapper/ValuePanelWrapper.tsx b/frontend/src/container/PanelWrapper/ValuePanelWrapper.tsx
new file mode 100644
index 0000000000..b5640d6fca
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/ValuePanelWrapper.tsx
@@ -0,0 +1,21 @@
+import GridValueComponent from 'container/GridValueComponent';
+import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
+
+import { PanelWrapperProps } from './panelWrapper.types';
+
+function ValuePanelWrapper({
+ widget,
+ queryResponse,
+}: PanelWrapperProps): JSX.Element {
+ const { yAxisUnit, thresholds } = widget;
+ const data = getUPlotChartData(queryResponse?.data?.payload);
+ return (
+
+ );
+}
+
+export default ValuePanelWrapper;
diff --git a/frontend/src/container/PanelWrapper/constants.ts b/frontend/src/container/PanelWrapper/constants.ts
new file mode 100644
index 0000000000..ec7388f027
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/constants.ts
@@ -0,0 +1,16 @@
+import { PANEL_TYPES } from 'constants/queryBuilder';
+
+import ListPanelWrapper from './ListPanelWrapper';
+import TablePanelWrapper from './TablePanelWrapper';
+import UplotPanelWrapper from './UplotPanelWrapper';
+import ValuePanelWrapper from './ValuePanelWrapper';
+
+export const PanelTypeVsPanelWrapper = {
+ [PANEL_TYPES.TIME_SERIES]: UplotPanelWrapper,
+ [PANEL_TYPES.TABLE]: TablePanelWrapper,
+ [PANEL_TYPES.LIST]: ListPanelWrapper,
+ [PANEL_TYPES.VALUE]: ValuePanelWrapper,
+ [PANEL_TYPES.TRACE]: null,
+ [PANEL_TYPES.EMPTY_WIDGET]: null,
+ [PANEL_TYPES.BAR]: UplotPanelWrapper,
+};
diff --git a/frontend/src/container/PanelWrapper/panelWrapper.types.ts b/frontend/src/container/PanelWrapper/panelWrapper.types.ts
new file mode 100644
index 0000000000..e5a53a85ff
--- /dev/null
+++ b/frontend/src/container/PanelWrapper/panelWrapper.types.ts
@@ -0,0 +1,22 @@
+import { WidgetGraphComponentProps } from 'container/GridCardLayout/GridCard/types';
+import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
+import { Dispatch, SetStateAction } from 'react';
+import { UseQueryResult } from 'react-query';
+import { SuccessResponse } from 'types/api';
+import { Widgets } from 'types/api/dashboard/getAll';
+import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
+
+export type PanelWrapperProps = {
+ queryResponse: UseQueryResult<
+ SuccessResponse,
+ Error
+ >;
+ widget: Widgets;
+ setRequestData?: WidgetGraphComponentProps['setRequestData'];
+ isFullViewMode?: boolean;
+ onToggleModelHandler?: () => void;
+ graphVisibility?: boolean[];
+ setGraphVisibility?: Dispatch>;
+ onClickHandler?: OnClickPluginOpts['onClick'];
+ onDragSelect: (start: number, end: number) => void;
+};
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
index 386786f70c..e7b00756f5 100644
--- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
@@ -1,11 +1,7 @@
import { Select, Spin } from 'antd';
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
// ** Constants
-import {
- idDivider,
- QueryBuilderKeys,
- selectValueDivider,
-} from 'constants/queryBuilder';
+import { idDivider, QueryBuilderKeys } from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import useDebounce from 'hooks/useDebounce';
@@ -83,11 +79,7 @@ export const GroupByFilter = memo(function GroupByFilter({
dataType={item.dataType || ''}
/>
),
- value: `${transformStringWithPrefix({
- str: item.key,
- prefix: item.type || '',
- condition: !item.isColumn,
- })}${selectValueDivider}${item.id}`,
+ value: `${item.id}`,
})) || [];
setOptionsData(options);
@@ -135,7 +127,8 @@ export const GroupByFilter = memo(function GroupByFilter({
const keys = await getAttributeKeys();
const groupByValues: BaseAutocompleteData[] = values.map((item) => {
- const [currentValue, id] = item.value.split(selectValueDivider);
+ const id = item.value;
+ const currentValue = item.value.split(idDivider)[0];
if (id && id.includes(idDivider)) {
const attribute = keys.find((item) => item.id === id);
@@ -174,11 +167,7 @@ export const GroupByFilter = memo(function GroupByFilter({
condition: !item.isColumn,
}),
)}`,
- value: `${transformStringWithPrefix({
- str: item.key,
- prefix: item.type || '',
- condition: !item.isColumn,
- })}${selectValueDivider}${item.id}`,
+ value: `${item.id}`,
}),
);
diff --git a/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.styles.scss b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.styles.scss
new file mode 100644
index 0000000000..51b4c266b2
--- /dev/null
+++ b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.styles.scss
@@ -0,0 +1,33 @@
+.resourceAttributesFilter-container {
+ display: flex;
+ align-items: center;
+ justify-content: stretch;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 16px;
+
+ .resource-attributes-selector {
+ flex: 1;
+ border-radius: 3px;
+
+ background-color: var(--bg-ink-400);
+ border: 1px solid #454c58;
+ }
+
+ .environment-selector {
+ min-width: 200px;
+ }
+
+ .ant-form-item {
+ margin-bottom: 0;
+ }
+}
+
+.lightMode {
+ .resourceAttributesFilter-container {
+ .resource-attributes-selector {
+ border: 1px solid #d9d9d9;
+ background: var(--bg-vanilla-100);
+ }
+ }
+}
diff --git a/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
index a61a0ce0ee..4211291742 100644
--- a/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
+++ b/frontend/src/container/ResourceAttributesFilter/ResourceAttributesFilter.tsx
@@ -1,10 +1,17 @@
+import './ResourceAttributesFilter.styles.scss';
+
import { CloseCircleFilled } from '@ant-design/icons';
import { Button, Select, Spin } from 'antd';
import useResourceAttribute, {
isResourceEmpty,
} from 'hooks/useResourceAttribute';
-import { convertMetricKeyToTrace } from 'hooks/useResourceAttribute/utils';
-import { ReactNode, useMemo } from 'react';
+import {
+ convertMetricKeyToTrace,
+ getEnvironmentTagKeys,
+ getEnvironmentTagValues,
+} from 'hooks/useResourceAttribute/utils';
+import { ReactNode, useEffect, useMemo, useState } from 'react';
+import { SelectOption } from 'types/common/select';
import { popupContainer } from 'utils/selectPopupContainer';
import { v4 as uuid } from 'uuid';
@@ -22,60 +29,129 @@ function ResourceAttributesFilter({
handleClearAll,
handleFocus,
handleChange,
+ handleEnvironmentChange,
selectedQuery,
optionsData,
loading,
} = useResourceAttribute();
- const isEmpty = useMemo(
- () => isResourceEmpty(queries, staging, selectedQuery),
- [queries, selectedQuery, staging],
+ const [environments, setEnvironments] = useState<
+ SelectOption[]
+ >([]);
+
+ const [selectedEnvironments, setSelectedEnvironments] = useState([]);
+
+ const queriesExcludingEnvironment = useMemo(
+ () =>
+ queries.filter(
+ (query) => query.tagKey !== 'resource_deployment_environment',
+ ),
+ [queries],
);
- return (
-
-
- {queries.map((query) => (
-
- ))}
- {staging.map((query, idx) => (
-
- {idx === 0 ? convertMetricKeyToTrace(query) : query}
-
- ))}
-
-
+ useEffect(() => {
+ const resourceDeploymentEnvironmentQuery = queries.filter(
+ (query) => query.tagKey === 'resource_deployment_environment',
+ );
+
+ if (resourceDeploymentEnvironmentQuery?.length > 0) {
+ setSelectedEnvironments(resourceDeploymentEnvironmentQuery[0].tagValue);
+ } else {
+ setSelectedEnvironments([]);
+ }
+ }, [queries]);
+
+ useEffect(() => {
+ getEnvironmentTagKeys().then((tagKeys) => {
+ if (tagKeys && Array.isArray(tagKeys) && tagKeys.length > 0) {
+ getEnvironmentTagValues().then((tagValues) => {
+ setEnvironments(tagValues);
+ });
+ }
+ });
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+ {queriesExcludingEnvironment.map((query) => (
+
+ ))}
+ {staging.map((query, idx) => (
+
+ {idx === 0 ? convertMetricKeyToTrace(query) : query}
+
+ ))}
+
+
+
+
);
}
diff --git a/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx b/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
index 363e6d5143..b2babd78b5 100644
--- a/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
+++ b/frontend/src/container/ResourceAttributesFilter/components/QueryChip/QueryChip.tsx
@@ -12,7 +12,10 @@ function QueryChip({ queryData, onClose }: IQueryChipProps): JSX.Element {
{convertMetricKeyToTrace(queryData.tagKey)}
{queryData.operator}
-
+
{queryData.tagValue.join(', ')}
diff --git a/frontend/src/container/ResourceAttributesFilter/styles.ts b/frontend/src/container/ResourceAttributesFilter/styles.ts
index c1dcd863f2..390190d2e4 100644
--- a/frontend/src/container/ResourceAttributesFilter/styles.ts
+++ b/frontend/src/container/ResourceAttributesFilter/styles.ts
@@ -7,9 +7,9 @@ export const SearchContainer = styled.div`
display: flex;
align-items: center;
gap: 0.2rem;
- padding: 0.2rem;
- margin: 1rem 0;
- border: 1px solid #ccc5;
+ padding: 0 0.2rem;
+ box-sizing: border-box;
+ border-radius: 3px;
`;
export const QueryChipContainer = styled.span`
diff --git a/frontend/src/container/ServiceApplication/types.ts b/frontend/src/container/ServiceApplication/types.ts
index 0717538cb8..4733b3053a 100644
--- a/frontend/src/container/ServiceApplication/types.ts
+++ b/frontend/src/container/ServiceApplication/types.ts
@@ -1,6 +1,9 @@
import { ServiceDataProps } from 'api/metrics/getTopLevelOperations';
import { Time } from 'container/TopNav/DateTimeSelection/config';
-import { Time as TimeV2 } from 'container/TopNav/DateTimeSelectionV2/config';
+import {
+ CustomTimeType,
+ Time as TimeV2,
+} from 'container/TopNav/DateTimeSelectionV2/config';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
@@ -25,7 +28,7 @@ export interface GetQueryRangeRequestDataProps {
topLevelOperations: [keyof ServiceDataProps, string[]][];
maxTime: number;
minTime: number;
- globalSelectedInterval: Time | TimeV2;
+ globalSelectedInterval: Time | TimeV2 | CustomTimeType;
}
export interface GetServiceListFromQueryProps {
diff --git a/frontend/src/container/SideNav/SideNav.styles.scss b/frontend/src/container/SideNav/SideNav.styles.scss
index 2cc32e12f3..b796c9727c 100644
--- a/frontend/src/container/SideNav/SideNav.styles.scss
+++ b/frontend/src/container/SideNav/SideNav.styles.scss
@@ -78,36 +78,65 @@
box-shadow: none !important;
}
}
+ .nav-wrapper {
+ height: calc(100% - 52px);
- .secondary-nav-items {
- border-top: 1px solid var(--bg-slate-400);
- padding: 8px 0;
- max-width: 100%;
- position: fixed;
- bottom: 0;
- left: 0;
- width: 240px;
+ .primary-nav-items {
+ max-height: 65%;
+ overflow-y: auto;
+ overflow-x: hidden;
- transition: all 0.3s, background 0s, border 0s;
-
- // position: relative;
-
- .collapse-expand-handlers {
- position: absolute;
-
- top: -9px;
- right: -9px;
- cursor: pointer;
-
- display: none;
-
- transition: display 0.3s;
-
- svg {
- fill: var(--bg-vanilla-400);
- color: var(--bg-slate-300);
+ &::-webkit-scrollbar {
+ width: 0.1rem;
}
}
+ .secondary-nav-items {
+ max-height: 35%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ border-top: 1px solid var(--bg-slate-400);
+ padding: 8px 0;
+ max-width: 100%;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 240px;
+
+ transition: all 0.3s, background 0s, border 0s;
+
+ &::-webkit-scrollbar {
+ width: 0.1rem;
+ }
+
+ .collapse-expand-handlers {
+ position: absolute;
+
+ top: -9px;
+ right: -9px;
+ cursor: pointer;
+
+ display: none;
+
+ transition: display 0.3s;
+
+ svg {
+ fill: var(--bg-vanilla-400);
+ color: var(--bg-slate-300);
+ }
+ }
+ }
+ }
+
+ .nav-wrapper-cloud {
+ height: calc(100% - 88px);
+
+ .secondary-nav-items {
+ max-height: 30%;
+ }
+
+ .primary-nav-items {
+ max-height: 70%;
+ }
}
&.collapsed {
@@ -157,13 +186,15 @@
}
}
- .secondary-nav-items {
- border-top: 1px solid var(--bg-vanilla-400);
+ .nav-wrapper {
+ .secondary-nav-items {
+ border-top: 1px solid var(--bg-vanilla-400);
- .collapse-expand-handlers {
- svg {
- color: var(--bg-slate-300);
- fill: var(--bg-vanilla-400);
+ .collapse-expand-handlers {
+ svg {
+ color: var(--bg-slate-300);
+ fill: var(--bg-vanilla-400);
+ }
}
}
}
diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx
index 665a406710..8ff08ca85e 100644
--- a/frontend/src/container/SideNav/SideNav.tsx
+++ b/frontend/src/container/SideNav/SideNav.tsx
@@ -271,6 +271,17 @@ function SideNav({
}
}, [isCloudUserVal, isEnterprise, isFetching]);
+ useEffect(() => {
+ if (!isCloudUserVal) {
+ let updatedMenuItems = [...menuItems];
+ updatedMenuItems = updatedMenuItems.filter(
+ (item) => item.key !== ROUTES.INTEGRATIONS,
+ );
+ setMenuItems(updatedMenuItems);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const [isCurrentOrgSettings] = useComponentPermission(
['current_org_settings'],
role,
@@ -364,90 +375,92 @@ function SideNav({
)}
-
-
-
- {licenseData && !isLicenseActive && (
-
- )}
-
- {userManagementMenuItems.map(
- (item, index): JSX.Element => (
+
+
+ {menuItems.map((item, index) => (
{
- handleUserManagentMenuItemClick(item?.key as string, event);
+ isActive={activeMenuKey === item.key}
+ onClick={(event): void => {
+ handleMenuItemClick(event, item);
}}
/>
- ),
- )}
+ ))}
+
- {inviteMembers && (
+
{
- if (isCtrlMetaKey(event)) {
- openInNewTab(`${inviteMemberMenuItem.key}`);
- } else {
- history.push(`${inviteMemberMenuItem.key}`);
- }
- }}
+ key="keyboardShortcuts"
+ item={shortcutMenuItem}
+ isActive={false}
+ onClick={onClickShortcuts}
/>
- )}
- {user && (
- {
- handleUserManagentMenuItemClick(
- userSettingsMenuItem?.key as string,
- event,
- );
- }}
- />
- )}
-
-
- {collapsed ? (
-
- ) : (
-
+ {licenseData && !isLicenseActive && (
+
)}
+
+ {userManagementMenuItems.map(
+ (item, index): JSX.Element => (
+
{
+ handleUserManagentMenuItemClick(item?.key as string, event);
+ }}
+ />
+ ),
+ )}
+
+ {inviteMembers && (
+ {
+ if (isCtrlMetaKey(event)) {
+ openInNewTab(`${inviteMemberMenuItem.key}`);
+ } else {
+ history.push(`${inviteMemberMenuItem.key}`);
+ }
+ }}
+ />
+ )}
+
+ {user && (
+ {
+ handleUserManagentMenuItemClick(
+ userSettingsMenuItem?.key as string,
+ event,
+ );
+ }}
+ />
+ )}
+
+
+ {collapsed ? (
+
+ ) : (
+
+ )}
+
diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx
index ed6f10b10a..38529a7f3b 100644
--- a/frontend/src/container/SideNav/menuItems.tsx
+++ b/frontend/src/container/SideNav/menuItems.tsx
@@ -16,6 +16,7 @@ import {
ScrollText,
Settings,
Slack,
+ Unplug,
// Unplug,
UserPlus,
} from 'lucide-react';
@@ -90,11 +91,11 @@ const menuItems: SidebarItem[] = [
label: 'Alerts',
icon:
,
},
- // {
- // key: ROUTES.INTEGRATIONS_INSTALLED,
- // label: 'Integrations',
- // icon:
,
- // },
+ {
+ key: ROUTES.INTEGRATIONS,
+ label: 'Integrations',
+ icon:
,
+ },
{
key: ROUTES.ALL_ERROR,
label: 'Exceptions',
@@ -127,7 +128,6 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record
= {
[ROUTES.TRACES_EXPLORER]: ROUTES.TRACE,
[ROUTES.TRACE_EXPLORER]: ROUTES.TRACE,
[ROUTES.LOGS_BASE]: ROUTES.LOGS_EXPLORER,
- [ROUTES.INTEGRATIONS_BASE]: ROUTES.INTEGRATIONS_INSTALLED,
};
export default menuItems;
diff --git a/frontend/src/container/TopNav/AutoRefresh/config.ts b/frontend/src/container/TopNav/AutoRefresh/config.ts
index 64aaca6c64..99c9db8ca5 100644
--- a/frontend/src/container/TopNav/AutoRefresh/config.ts
+++ b/frontend/src/container/TopNav/AutoRefresh/config.ts
@@ -1,7 +1,7 @@
import GetMinMax, { GetMinMaxPayload } from 'lib/getMinMax';
import { Time } from '../DateTimeSelection/config';
-import { Time as TimeV2 } from '../DateTimeSelectionV2/config';
+import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config';
export const options: IOptions[] = [
{
@@ -68,7 +68,7 @@ export interface IOptions {
}
export const getMinMax = (
- selectedTime: Time | TimeV2,
+ selectedTime: Time | TimeV2 | CustomTimeType,
minTime: number,
maxTime: number,
): GetMinMaxPayload =>
diff --git a/frontend/src/container/TopNav/AutoRefreshV2/config.ts b/frontend/src/container/TopNav/AutoRefreshV2/config.ts
index a84f932fbc..8baae51d01 100644
--- a/frontend/src/container/TopNav/AutoRefreshV2/config.ts
+++ b/frontend/src/container/TopNav/AutoRefreshV2/config.ts
@@ -1,7 +1,7 @@
import GetMinMax, { GetMinMaxPayload } from 'lib/getMinMax';
import { Time } from '../DateTimeSelection/config';
-import { Time as TimeV2 } from '../DateTimeSelectionV2/config';
+import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config';
export const options: IOptions[] = [
{
@@ -68,7 +68,7 @@ export interface IOptions {
}
export const getMinMax = (
- selectedTime: Time | TimeV2,
+ selectedTime: Time | TimeV2 | CustomTimeType,
minTime: number,
maxTime: number,
): GetMinMaxPayload =>
diff --git a/frontend/src/container/TopNav/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts
index 3618686c95..102fe00c43 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/config.ts
+++ b/frontend/src/container/TopNav/DateTimeSelection/config.ts
@@ -1,16 +1,18 @@
import ROUTES from 'constants/routes';
-type FiveMin = '5min';
-type TenMin = '10min';
-type FifteenMin = '15min';
-type ThirtyMin = '30min';
-type OneMin = '1min';
-type SixHour = '6hr';
-type OneHour = '1hr';
-type FourHour = '4hr';
-type OneDay = '1day';
-type ThreeDay = '3days';
-type OneWeek = '1week';
+type FiveMin = '5m';
+type TenMin = '10m';
+type FifteenMin = '15m';
+type ThirtyMin = '30m';
+type OneMin = '1m';
+type SixHour = '6h';
+type OneHour = '1h';
+type FourHour = '4h';
+type ThreeHour = '3h';
+type TwelveHour = '12h';
+type OneDay = '1d';
+type ThreeDay = '3d';
+type OneWeek = '1w';
type Custom = 'custom';
export type Time =
@@ -22,37 +24,62 @@ export type Time =
| FourHour
| SixHour
| OneHour
+ | ThreeHour
| Custom
| OneWeek
| OneDay
+ | TwelveHour
| ThreeDay;
export const Options: Option[] = [
- { value: '5min', label: 'Last 5 min' },
- { value: '15min', label: 'Last 15 min' },
- { value: '30min', label: 'Last 30 min' },
- { value: '1hr', label: 'Last 1 hour' },
- { value: '6hr', label: 'Last 6 hour' },
- { value: '1day', label: 'Last 1 day' },
- { value: '3days', label: 'Last 3 days' },
- { value: '1week', label: 'Last 1 week' },
+ { value: '5m', label: 'Last 5 min' },
+ { value: '15m', label: 'Last 15 min' },
+ { value: '30m', label: 'Last 30 min' },
+ { value: '1h', label: 'Last 1 hour' },
+ { value: '6h', label: 'Last 6 hour' },
+ { value: '1d', label: 'Last 1 day' },
+ { value: '3d', label: 'Last 3 days' },
+ { value: '1w', label: 'Last 1 week' },
{ value: 'custom', label: 'Custom' },
];
+type TimeFrame = {
+ '5min': string;
+ '15min': string;
+ '30min': string;
+ '1hr': string;
+ '6hr': string;
+ '1day': string;
+ '3days': string;
+ '1week': string;
+ [key: string]: string; // Index signature to allow any string as index
+};
+
+export const RelativeTimeMap: TimeFrame = {
+ '5min': '5m',
+ '15min': '15m',
+ '30min': '30m',
+ '1hr': '1h',
+ '6hr': '6h',
+ '1day': '1d',
+ '3days': '3d',
+ '1week': '1w',
+};
+
export interface Option {
value: Time;
label: string;
}
export const RelativeDurationOptions: Option[] = [
- { value: '5min', label: 'Last 5 min' },
- { value: '15min', label: 'Last 15 min' },
- { value: '30min', label: 'Last 30 min' },
- { value: '1hr', label: 'Last 1 hour' },
- { value: '6hr', label: 'Last 6 hour' },
- { value: '1day', label: 'Last 1 day' },
- { value: '3days', label: 'Last 3 days' },
- { value: '1week', label: 'Last 1 week' },
+ { value: '5m', label: 'Last 5 min' },
+ { value: '15m', label: 'Last 15 min' },
+ { value: '30m', label: 'Last 30 min' },
+ { value: '1h', label: 'Last 1 hour' },
+ { value: '6h', label: 'Last 6 hour' },
+ { value: '1d', label: 'Last 1 day' },
+ { value: '3d', label: 'Last 3 days' },
+ { value: '1w', label: 'Last 1 week' },
];
export const getDefaultOption = (route: string): Time => {
diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
index 614e977a12..3d023959f2 100644
--- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx
@@ -28,7 +28,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import AutoRefresh from '../AutoRefresh';
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
-import { Time as TimeV2 } from '../DateTimeSelectionV2/config';
+import { CustomTimeType, Time as TimeV2 } from '../DateTimeSelectionV2/config';
import {
getDefaultOption,
getOptions,
@@ -122,7 +122,7 @@ function DateTimeSelection({
const getInputLabel = (
startTime?: Dayjs,
endTime?: Dayjs,
- timeInterval: Time | TimeV2 = '15min',
+ timeInterval: Time | TimeV2 | CustomTimeType = '15m',
): string | Time => {
if (startTime && endTime && timeInterval === 'custom') {
const format = 'YYYY/MM/DD HH:mm';
@@ -225,7 +225,7 @@ function DateTimeSelection({
[location.pathname],
);
- const onSelectHandler = (value: Time | TimeV2): void => {
+ const onSelectHandler = (value: Time | TimeV2 | CustomTimeType): void => {
if (value !== 'custom') {
updateTimeInterval(value);
updateLocalStorageForRoutes(value);
@@ -358,7 +358,7 @@ function DateTimeSelection({
}}
selectedTime={selectedTime}
onValidCustomDateChange={(dateTime): void =>
- onCustomDateHandler(dateTime as DateTimeRangeType)
+ onCustomDateHandler(dateTime.time as DateTimeRangeType)
}
selectedValue={getInputLabel(
dayjs(minTime / 1000000),
@@ -406,7 +406,7 @@ function DateTimeSelection({
interface DispatchProps {
updateTimeInterval: (
- interval: Time | TimeV2,
+ interval: Time | TimeV2 | CustomTimeType,
dateTimeRange?: [number, number],
) => (dispatch: Dispatch) => void;
globalTimeLoading: () => void;
diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss
index bd4cc3cdb1..22efca5009 100644
--- a/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss
+++ b/frontend/src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss
@@ -54,9 +54,61 @@
}
}
}
+
+ .share-link-btn {
+ height: 34px;
+ }
+
+ .shareable-link-popover {
+ margin-left: 8px;
+ }
}
-.date-time-root {
+.share-modal-content {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 16px;
+ width: 420px;
+
+ .absolute-relative-time-toggler-container {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+
+ .absolute-relative-time-toggler {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ }
+
+ .absolute-relative-time-error {
+ font-size: 12px;
+ color: var(--bg-amber-600);
+ }
+
+ .share-link {
+ display: flex;
+ align-items: center;
+
+ .share-url {
+ flex: 1;
+ border: 1px solid var(--bg-slate-400);
+ border-radius: 2px;
+ background: var(--bg-ink-300);
+ height: 32px;
+ padding: 6px 8px;
+ }
+
+ .copy-url-btn {
+ width: 32px;
+ }
+ }
+}
+
+.date-time-root,
+.shareable-link-popover-root {
.ant-popover-inner {
border-radius: 4px !important;
border: 1px solid var(--bg-slate-400);
@@ -185,7 +237,8 @@
}
}
- .date-time-root {
+ .date-time-root,
+ .shareable-link-popover-root {
.ant-popover-inner {
border: 1px solid var(--bg-vanilla-400);
background: var(--bg-vanilla-100) !important;
@@ -234,4 +287,13 @@
}
}
}
+
+ .share-modal-content {
+ .share-link {
+ .share-url {
+ border: 1px solid var(--bg-vanilla-300);
+ background: var(--bg-vanilla-100);
+ }
+ }
+ }
}
diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts
index 6231505580..eefa31475c 100644
--- a/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts
+++ b/frontend/src/container/TopNav/DateTimeSelectionV2/config.ts
@@ -1,24 +1,24 @@
/* eslint-disable sonarjs/no-duplicate-string */
import ROUTES from 'constants/routes';
-type FiveMin = '5min';
-type TenMin = '10min';
-type FifteenMin = '15min';
-type ThirtyMin = '30min';
-type FortyFiveMin = '45min';
-type OneMin = '1min';
-type ThreeHour = '3hr';
-type SixHour = '6hr';
-type OneHour = '1hr';
-type FourHour = '4hr';
-type TwelveHour = '12hr';
-type OneDay = '1day';
-type ThreeDay = '3days';
-type FourDay = '4days';
-type TenDay = '10days';
-type OneWeek = '1week';
-type TwoWeek = '2weeks';
-type SixWeek = '6weeks';
+type FiveMin = '5m';
+type TenMin = '10m';
+type FifteenMin = '15m';
+type ThirtyMin = '30m';
+type FortyFiveMin = '45m';
+type OneMin = '1m';
+type ThreeHour = '3h';
+type SixHour = '6h';
+type OneHour = '1h';
+type FourHour = '4h';
+type TwelveHour = '12h';
+type OneDay = '1d';
+type ThreeDay = '3d';
+type FourDay = '4d';
+type TenDay = '10d';
+type OneWeek = '1w';
+type TwoWeek = '2w';
+type SixWeek = '6w';
type TwoMonths = '2months';
type Custom = 'custom';
@@ -44,15 +44,19 @@ export type Time =
| TwoWeek
| TwoMonths;
+export type TimeUnit = 'm' | 'h' | 'd' | 'w';
+
+export type CustomTimeType = `${string}${TimeUnit}`;
+
export const Options: Option[] = [
- { value: '5min', label: 'Last 5 minutes' },
- { value: '15min', label: 'Last 15 minutes' },
- { value: '30min', label: 'Last 30 minutes' },
- { value: '1hr', label: 'Last 1 hour' },
- { value: '6hr', label: 'Last 6 hours' },
- { value: '1day', label: 'Last 1 day' },
- { value: '3days', label: 'Last 3 days' },
- { value: '1week', label: 'Last 1 week' },
+ { value: '5m', label: 'Last 5 minutes' },
+ { value: '15m', label: 'Last 15 minutes' },
+ { value: '30m', label: 'Last 30 minutes' },
+ { value: '1h', label: 'Last 1 hour' },
+ { value: '6h', label: 'Last 6 hours' },
+ { value: '1d', label: 'Last 1 day' },
+ { value: '3d', label: 'Last 3 days' },
+ { value: '1w', label: 'Last 1 week' },
{ value: 'custom', label: 'Custom' },
];
@@ -61,36 +65,92 @@ export interface Option {
label: string;
}
+export const OLD_RELATIVE_TIME_VALUES = [
+ '1min',
+ '10min',
+ '15min',
+ '1hr',
+ '30min',
+ '45min',
+ '5min',
+ '1day',
+ '3days',
+ '4days',
+ '10days',
+ '1week',
+ '2weeks',
+ '6weeks',
+ '3hr',
+ '4hr',
+ '6hr',
+ '12hr',
+];
+
export const RelativeDurationOptions: Option[] = [
- { value: '5min', label: 'Last 5 minutes' },
- { value: '15min', label: 'Last 15 minutes' },
- { value: '30min', label: 'Last 30 minutes' },
- { value: '1hr', label: 'Last 1 hour' },
- { value: '6hr', label: 'Last 6 hour' },
- { value: '1day', label: 'Last 1 day' },
- { value: '3days', label: 'Last 3 days' },
- { value: '1week', label: 'Last 1 week' },
+ { value: '5m', label: 'Last 5 minutes' },
+ { value: '15m', label: 'Last 15 minutes' },
+ { value: '30m', label: 'Last 30 minutes' },
+ { value: '1h', label: 'Last 1 hour' },
+ { value: '6h', label: 'Last 6 hour' },
+ { value: '1d', label: 'Last 1 day' },
+ { value: '3d', label: 'Last 3 days' },
+ { value: '1w', label: 'Last 1 week' },
];
export const RelativeDurationSuggestionOptions: Option[] = [
- { value: '3hr', label: '3h' },
- { value: '4days', label: '4d' },
- { value: '6weeks', label: '6w' },
- { value: '12hr', label: '12 hours' },
- { value: '10days', label: '10d' },
- { value: '2weeks', label: '2 weeks' },
+ { value: '3h', label: 'Last 3 hours' },
+ { value: '4d', label: 'Last 4 days' },
+ { value: '6w', label: 'Last 6 weeks' },
+ { value: '12h', label: 'Last 12 hours' },
+ { value: '10d', label: 'Last 10 days' },
+ { value: '2w', label: 'Last 2 weeks' },
{ value: '2months', label: 'Last 2 months' },
- { value: '1day', label: 'today' },
+ { value: '1d', label: 'today' },
];
export const FixedDurationSuggestionOptions: Option[] = [
- { value: '45min', label: '45m' },
- { value: '12hr', label: '12 hours' },
- { value: '10days', label: '10d' },
- { value: '2weeks', label: '2 weeks' },
+ { value: '45m', label: 'Last 45 mins' },
+ { value: '12h', label: 'Last 12 hours' },
+ { value: '10d', label: 'Last 10 days' },
+ { value: '2w', label: 'Last 2 weeks' },
{ value: '2months', label: 'Last 2 months' },
- { value: '1day', label: 'today' },
+ { value: '1d', label: 'today' },
];
+export const convertOldTimeToNewValidCustomTimeFormat = (
+ time: string,
+): CustomTimeType => {
+ const regex = /^(\d+)([a-zA-Z]+)/;
+ const match = regex.exec(time);
+
+ if (match) {
+ let unit = 'm';
+
+ switch (match[2]) {
+ case 'min':
+ unit = 'm';
+ break;
+ case 'hr':
+ unit = 'h';
+ break;
+ case 'day':
+ case 'days':
+ unit = 'd';
+ break;
+ case 'week':
+ case 'weeks':
+ unit = 'w';
+ break;
+
+ default:
+ break;
+ }
+
+ return `${match[1]}${unit}` as CustomTimeType;
+ }
+
+ return '30m';
+};
+
export const getDefaultOption = (route: string): Time => {
if (route === ROUTES.SERVICE_MAP) {
return RelativeDurationOptions[2].value;
@@ -139,9 +199,7 @@ export const routesToSkip = [
ROUTES.TRACES_EXPLORER,
ROUTES.TRACES_SAVE_VIEWS,
ROUTES.SHORTCUTS,
- ROUTES.INTEGRATIONS_BASE,
- ROUTES.INTEGRATIONS_INSTALLED,
- ROUTES.INTEGRATIONS_MARKETPLACE,
+ ROUTES.INTEGRATIONS,
];
export const routesToDisable = [ROUTES.LOGS_EXPLORER, ROUTES.LIVE_LOGS];
diff --git a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
index 3ef5125ad7..3fa36610a0 100644
--- a/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
+++ b/frontend/src/container/TopNav/DateTimeSelectionV2/index.tsx
@@ -1,7 +1,8 @@
import './DateTimeSelectionV2.styles.scss';
import { SyncOutlined } from '@ant-design/icons';
-import { Button } from 'antd';
+import { Color } from '@signozhq/design-tokens';
+import { Button, Popover, Switch, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import CustomTimePicker from 'components/CustomTimePicker/CustomTimePicker';
@@ -26,10 +27,12 @@ import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import history from 'lib/history';
import { isObject } from 'lodash-es';
+import { Check, Copy, Info, Send } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
import { connect, useSelector } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
+import { useCopyToClipboard } from 'react-use';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions';
@@ -42,9 +45,12 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import AutoRefresh from '../AutoRefreshV2';
import { DateTimeRangeType } from '../CustomDateTimeModal';
import {
+ convertOldTimeToNewValidCustomTimeFormat,
+ CustomTimeType,
getDefaultOption,
getOptions,
LocalStorageTimeRange,
+ OLD_RELATIVE_TIME_VALUES,
Time,
TimeRange,
} from './config';
@@ -66,6 +72,10 @@ function DateTimeSelection({
const searchStartTime = urlQuery.get('startTime');
const searchEndTime = urlQuery.get('endTime');
const queryClient = useQueryClient();
+ const [enableAbsoluteTime, setEnableAbsoluteTime] = useState(false);
+ const [isValidteRelativeTime, setIsValidteRelativeTime] = useState(false);
+ const [, handleCopyToClipboard] = useCopyToClipboard();
+ const [isURLCopied, setIsURLCopied] = useState(false);
const {
localstorageStartTime,
@@ -178,7 +188,7 @@ function DateTimeSelection({
const getInputLabel = (
startTime?: Dayjs,
endTime?: Dayjs,
- timeInterval: Time = '15min',
+ timeInterval: Time | CustomTimeType = '15m',
): string | Time => {
if (startTime && endTime && timeInterval === 'custom') {
const format = 'DD/MM/YYYY HH:mm';
@@ -284,28 +294,38 @@ function DateTimeSelection({
[location.pathname],
);
- const onSelectHandler = (value: Time): void => {
+ const onSelectHandler = (value: Time | CustomTimeType): void => {
if (value !== 'custom') {
setIsOpen(false);
updateTimeInterval(value);
updateLocalStorageForRoutes(value);
+ setIsValidteRelativeTime(true);
if (refreshButtonHidden) {
setRefreshButtonHidden(false);
}
} else {
setRefreshButtonHidden(true);
setCustomDTPickerVisible(true);
+ setIsValidteRelativeTime(false);
+ setEnableAbsoluteTime(false);
+
+ return;
}
const { maxTime, minTime } = GetMinMax(value, getTime());
if (!isLogsExplorerPage) {
- urlQuery.set(QueryParams.startTime, minTime.toString());
- urlQuery.set(QueryParams.endTime, maxTime.toString());
+ urlQuery.delete('startTime');
+ urlQuery.delete('endTime');
+
+ urlQuery.set(QueryParams.relativeTime, value);
+
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
}
+ // For logs explorer - time range handling is managed in useCopyLogLink.ts:52
+
if (!stagedQuery) {
return;
}
@@ -319,18 +339,22 @@ function DateTimeSelection({
};
const onCustomDateHandler = (dateTimeRange: DateTimeRangeType): void => {
+ // console.log('dateTimeRange', dateTimeRange);
if (dateTimeRange !== null) {
const [startTimeMoment, endTimeMoment] = dateTimeRange;
if (startTimeMoment && endTimeMoment) {
const startTime = startTimeMoment;
const endTime = endTimeMoment;
setCustomDTPickerVisible(false);
+
updateTimeInterval('custom', [
startTime.toDate().getTime(),
endTime.toDate().getTime(),
]);
+
setLocalStorageKey('startTime', startTime.toString());
setLocalStorageKey('endTime', endTime.toString());
+
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
if (!isLogsExplorerPage) {
@@ -339,6 +363,7 @@ function DateTimeSelection({
startTime?.toDate().getTime().toString(),
);
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
+ urlQuery.delete(QueryParams.relativeTime);
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.replace(generatedUrl);
}
@@ -346,6 +371,57 @@ function DateTimeSelection({
}
};
+ const onValidCustomDateHandler = (dateTimeStr: CustomTimeType): void => {
+ setIsOpen(false);
+ updateTimeInterval(dateTimeStr);
+ updateLocalStorageForRoutes(dateTimeStr);
+
+ urlQuery.delete('startTime');
+ urlQuery.delete('endTime');
+
+ setIsValidteRelativeTime(true);
+
+ const { maxTime, minTime } = GetMinMax(dateTimeStr, getTime());
+
+ if (!isLogsExplorerPage) {
+ urlQuery.delete('startTime');
+ urlQuery.delete('endTime');
+
+ urlQuery.set(QueryParams.relativeTime, dateTimeStr);
+
+ const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
+ history.replace(generatedUrl);
+ }
+
+ if (!stagedQuery) {
+ return;
+ }
+
+ // the second boolean param directs the qb about the time change so to merge the query and retain the current state
+ initQueryBuilderData(updateStepInterval(stagedQuery, maxTime, minTime), true);
+ };
+
+ const getCustomOrIntervalTime = (
+ time: Time,
+ currentRoute: string,
+ ): Time | CustomTimeType => {
+ if (searchEndTime !== null && searchStartTime !== null) {
+ return 'custom';
+ }
+ if (
+ (localstorageEndTime === null || localstorageStartTime === null) &&
+ time === 'custom'
+ ) {
+ return getDefaultOption(currentRoute);
+ }
+
+ if (OLD_RELATIVE_TIME_VALUES.indexOf(time) > -1) {
+ return convertOldTimeToNewValidCustomTimeFormat(time);
+ }
+
+ return time;
+ };
+
// this is triggred when we change the routes and based on that we are changing the default options
useEffect(() => {
const metricsTimeDuration = getLocalStorageKey(
@@ -365,21 +441,9 @@ function DateTimeSelection({
const currentOptions = getOptions(currentRoute);
setOptions(currentOptions);
- const getCustomOrIntervalTime = (time: Time): Time => {
- if (searchEndTime !== null && searchStartTime !== null) {
- return 'custom';
- }
- if (
- (localstorageEndTime === null || localstorageStartTime === null) &&
- time === 'custom'
- ) {
- return getDefaultOption(currentRoute);
- }
+ const updatedTime = getCustomOrIntervalTime(time, currentRoute);
- return time;
- };
-
- const updatedTime = getCustomOrIntervalTime(time);
+ setIsValidteRelativeTime(updatedTime !== 'custom');
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
@@ -388,18 +452,113 @@ function DateTimeSelection({
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
if (updatedTime !== 'custom') {
- const { minTime, maxTime } = GetMinMax(updatedTime);
- urlQuery.set(QueryParams.startTime, minTime.toString());
- urlQuery.set(QueryParams.endTime, maxTime.toString());
+ urlQuery.delete('startTime');
+ urlQuery.delete('endTime');
+
+ urlQuery.set(QueryParams.relativeTime, updatedTime);
} else {
- urlQuery.set(QueryParams.startTime, preStartTime.toString());
- urlQuery.set(QueryParams.endTime, preEndTime.toString());
+ const startTime = preStartTime.toString();
+ const endTime = preEndTime.toString();
+
+ urlQuery.set(QueryParams.startTime, startTime);
+ urlQuery.set(QueryParams.endTime, endTime);
}
+
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
+
history.replace(generatedUrl);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname, updateTimeInterval, globalTimeLoading]);
+ // eslint-disable-next-line sonarjs/cognitive-complexity
+ const shareModalContent = (): JSX.Element => {
+ let currentUrl = window.location.href;
+
+ const startTime = urlQuery.get(QueryParams.startTime);
+ const endTime = urlQuery.get(QueryParams.endTime);
+ const isCustomTime = !!(startTime && endTime && selectedTime === 'custom');
+
+ if (enableAbsoluteTime || isCustomTime) {
+ if (selectedTime === 'custom') {
+ if (searchStartTime && searchEndTime) {
+ urlQuery.set(QueryParams.startTime, searchStartTime.toString());
+ urlQuery.set(QueryParams.endTime, searchEndTime.toString());
+ }
+ } else {
+ const { minTime, maxTime } = GetMinMax(selectedTime);
+
+ urlQuery.set(QueryParams.startTime, minTime.toString());
+ urlQuery.set(QueryParams.endTime, maxTime.toString());
+ }
+
+ urlQuery.delete(QueryParams.relativeTime);
+
+ currentUrl = `${window.location.origin}${
+ location.pathname
+ }?${urlQuery.toString()}`;
+ } else {
+ urlQuery.delete(QueryParams.startTime);
+ urlQuery.delete(QueryParams.endTime);
+
+ urlQuery.set(QueryParams.relativeTime, selectedTime);
+ currentUrl = `${window.location.origin}${
+ location.pathname
+ }?${urlQuery.toString()}`;
+ }
+
+ return (
+
+
+
+ {(selectedTime === 'custom' || !isValidteRelativeTime) && (
+
+ )}
+ {
+ setEnableAbsoluteTime(!enableAbsoluteTime);
+ }}
+ />
+
+
+
Enable Absolute Time
+
+
+ {(selectedTime === 'custom' || !isValidteRelativeTime) && (
+
+ Please select / enter valid relative time to toggle.
+
+ )}
+
+
+
+ {currentUrl}
+
+
+
+
+ );
+ };
+
return (
{!hasSelectedTimeError && !refreshButtonHidden && (
@@ -426,9 +585,12 @@ function DateTimeSelection({
setHasSelectedTimeError(hasError);
}}
selectedTime={selectedTime}
- onValidCustomDateChange={(dateTime): void =>
- onCustomDateHandler(dateTime as DateTimeRangeType)
- }
+ onValidCustomDateChange={(dateTime): void => {
+ onValidCustomDateHandler(dateTime.timeStr as CustomTimeType);
+ }}
+ onCustomTimeStatusUpdate={(isValid: boolean): void => {
+ setIsValidteRelativeTime(isValid);
+ }}
selectedValue={getInputLabel(
dayjs(minTime / 1000000),
dayjs(maxTime / 1000000),
@@ -457,6 +619,22 @@ function DateTimeSelection({
)}
+
+
+ }
+ >
+ Share
+
+
@@ -468,7 +646,7 @@ interface DateTimeSelectionV2Props {
}
interface DispatchProps {
updateTimeInterval: (
- interval: Time,
+ interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => (dispatch: Dispatch