From 1610b95b84d1e1bef8a70a704a9e26d14fdb666f Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 1 Apr 2024 19:09:16 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20onboarding=20flow=20-=20enable=20users?= =?UTF-8?q?=20to=20submit=20request=20for=20a=20new=20data=E2=80=A6=20(#47?= =?UTF-8?q?86)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: onboarding flow - enable users to submit request for a new data source , environment * chore: request data source to be available for all modules * chore: remove hardcoded value --- frontend/src/api/common/logEvent.ts | 28 +++ .../Onboarding.styles.scss | 21 +- .../Steps/DataSource/DataSource.styles.scss | 5 + .../Steps/DataSource/DataSource.tsx | 182 +++++++++++++----- .../EnvironmentDetails/EnvironmentDetails.tsx | 111 ++++++++++- frontend/src/periscope.scss | 5 + frontend/src/types/api/events/types.ts | 9 + frontend/src/typings/window.ts | 1 + 8 files changed, 311 insertions(+), 51 deletions(-) create mode 100644 frontend/src/api/common/logEvent.ts create mode 100644 frontend/src/types/api/events/types.ts diff --git a/frontend/src/api/common/logEvent.ts b/frontend/src/api/common/logEvent.ts new file mode 100644 index 000000000000..212d382d7765 --- /dev/null +++ b/frontend/src/api/common/logEvent.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { EventSuccessPayloadProps } from 'types/api/events/types'; + +const logEvent = async ( + eventName: string, + attributes: Record, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/event', { + eventName, + attributes, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default logEvent; diff --git a/frontend/src/container/OnboardingContainer/Onboarding.styles.scss b/frontend/src/container/OnboardingContainer/Onboarding.styles.scss index 4e00b629c891..018c9af35220 100644 --- a/frontend/src/container/OnboardingContainer/Onboarding.styles.scss +++ b/frontend/src/container/OnboardingContainer/Onboarding.styles.scss @@ -58,10 +58,15 @@ box-sizing: border-box; cursor: pointer; width: 400px; + transition: 0.3s; .ant-card-body { padding: 0px; } + + &:hover { + transform: scale(1.05); + } } .moduleTitleStyle { @@ -73,6 +78,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + text-align: center; } .moduleStyles.selected { @@ -107,8 +113,8 @@ .actionButtonsContainer { display: flex; - justify-content: space-between; align-items: center; + gap: 8px; box-sizing: border-box; align-items: center; } @@ -137,3 +143,16 @@ padding-left: 4px; } } + +.request-entity-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + border-radius: 4px; + border: 0.5px solid rgba(78, 116, 248, 0.2); + background: rgba(69, 104, 220, 0.1); + padding: 12px; + margin: 24px 0; +} diff --git a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss index a991bb216dfa..a3d8468559c1 100644 --- a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss +++ b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss @@ -22,6 +22,7 @@ div[class*='-setup-instructions-container'] { .form-container { display: flex; + flex-direction: column; align-items: flex-start; width: 100%; gap: 16px; @@ -36,3 +37,7 @@ div[class*='-setup-instructions-container'] { text-align: center; font-size: 12px; } + +.service-name-container { + width: 100%; +} diff --git a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx index 13296671ecf2..5a06cdef94d8 100644 --- a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx @@ -2,7 +2,9 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ import './DataSource.styles.scss'; -import { Card, Form, Input, Select, Typography } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; +import { Button, Card, Form, Input, Select, 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'; @@ -11,7 +13,10 @@ import { getSupportedFrameworks, hasFrameworks, } from 'container/OnboardingContainer/utils/dataSourceUtils'; +import { useNotifications } from 'hooks/useNotifications'; +import { Check } from 'lucide-react'; import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { popupContainer } from 'utils/selectPopupContainer'; export interface DataSourceType { @@ -23,6 +28,7 @@ export interface DataSourceType { export default function DataSource(): JSX.Element { const [form] = Form.useForm(); + const { t } = useTranslation(['common']); const { serviceName, @@ -42,6 +48,15 @@ export default function DataSource(): JSX.Element { DataSourceType[] >([]); + const requestedDataSourceName = Form.useWatch('requestedDataSourceName', form); + + const [ + isSubmittingRequestForDataSource, + setIsSubmittingRequestForDataSource, + ] = useState(false); + + const { notifications } = useNotifications(); + const [enableFrameworks, setEnableFrameworks] = useState(false); useEffect(() => { @@ -74,12 +89,49 @@ export default function DataSource(): JSX.Element { } }, [selectedModule, selectedDataSource]); + const handleRequestDataSourceSubmit = async (): Promise => { + try { + setIsSubmittingRequestForDataSource(true); + const response = await logEvent('Onboarding V2: Data Source Requested', { + module: selectedModule?.id, + dataSource: requestedDataSourceName, + }); + + if (response.statusCode === 200) { + notifications.success({ + message: 'Data Source Request Submitted', + }); + + form.setFieldValue('requestedDataSourceName', ''); + + setIsSubmittingRequestForDataSource(false); + } else { + notifications.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + + setIsSubmittingRequestForDataSource(false); + } + } catch (error) { + notifications.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + + setIsSubmittingRequestForDataSource(false); + } + }; + return (
* Select Data Source -
{supportedDataSources?.map((dataSource) => ( - {selectedModule?.id === useCases.APM.id && ( -
-
-
{ - const serviceName = form.getFieldValue('serviceName'); +
+
+ { + const serviceName = form.getFieldValue('serviceName'); - updateServiceName(serviceName); - }} - name="data-source-form" - style={{ minWidth: '300px' }} - layout="vertical" - validateTrigger="onBlur" - > - - - + updateServiceName(serviceName); + }} + name="data-source-form" + layout="vertical" + validateTrigger="onBlur" + > + {selectedModule?.id === useCases.APM.id && ( + <> + + + - {enableFrameworks && ( -
+ {enableFrameworks && ( +
+ + updateSelectedFramework(value)} - options={supportedframeworks} - /> + -
- )} - -
+ + +
+
+
- )} +
); } diff --git a/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx b/frontend/src/container/OnboardingContainer/Steps/EnvironmentDetails/EnvironmentDetails.tsx index f4f8381de774..02fac551dfad 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; @@ -33,16 +38,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 ( - <> +
* Select Environment @@ -80,11 +147,47 @@ export default function EnvironmentDetails(): JSX.Element { })}
+
+ + Cannot find what you’re looking for? Request a data source + + +
+ + + + + + +
+
+ {errorDetails && (
{errorDetails}
)} - + ); } diff --git a/frontend/src/periscope.scss b/frontend/src/periscope.scss index d32ac109732d..53c14deb53c5 100644 --- a/frontend/src/periscope.scss +++ b/frontend/src/periscope.scss @@ -30,6 +30,11 @@ background-color: #4566d6; box-shadow: 0 2px 0 rgba(62, 86, 245, 0.09); } + + + &:disabled { + opacity: 0.5; + } } .periscope-tab { diff --git a/frontend/src/types/api/events/types.ts b/frontend/src/types/api/events/types.ts new file mode 100644 index 000000000000..e5b8cd8bd089 --- /dev/null +++ b/frontend/src/types/api/events/types.ts @@ -0,0 +1,9 @@ +export interface EventSuccessPayloadProps { + status: string; + data: string; +} + +export interface EventRequestPayloadProps { + eventName: string; + attributes: Record; +} diff --git a/frontend/src/typings/window.ts b/frontend/src/typings/window.ts index 02197e24446d..3bff939c2dee 100644 --- a/frontend/src/typings/window.ts +++ b/frontend/src/typings/window.ts @@ -6,6 +6,7 @@ declare global { interface Window { store: Store; clarity: ClarityType; + Intercom: any; analytics: Record; __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose; }