feat: onboarding flow - enable users to submit request for a new data… (#4786)

* 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
This commit is contained in:
Yunus M 2024-04-01 19:09:16 +05:30 committed by GitHub
parent 8c02f8ec31
commit 1610b95b84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 311 additions and 51 deletions

View File

@ -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<string, unknown>,
): Promise<SuccessResponse<EventSuccessPayloadProps> | 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;

View File

@ -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;
}

View File

@ -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%;
}

View File

@ -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<void> => {
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 (
<div className="module-container">
<Typography.Text className="data-source-title">
<span className="required-symbol">*</span> Select Data Source
</Typography.Text>
<div className="supported-languages-container">
{supportedDataSources?.map((dataSource) => (
<Card
@ -112,56 +164,94 @@ export default function DataSource(): JSX.Element {
))}
</div>
{selectedModule?.id === useCases.APM.id && (
<div className="form-container">
<div className="service-name-container">
<Form
initialValues={{
serviceName,
selectFramework: selectedFramework,
}}
form={form}
onValuesChange={(): void => {
const serviceName = form.getFieldValue('serviceName');
<div className="form-container">
<div className="service-name-container">
<Form
initialValues={{
serviceName,
selectFramework: selectedFramework,
}}
form={form}
onValuesChange={(): void => {
const serviceName = form.getFieldValue('serviceName');
updateServiceName(serviceName);
}}
name="data-source-form"
style={{ minWidth: '300px' }}
layout="vertical"
validateTrigger="onBlur"
>
<Form.Item
name="serviceName"
label="Service Name"
rules={[{ required: true, message: 'Please enter service name' }]}
validateTrigger="onBlur"
>
<Input autoFocus />
</Form.Item>
updateServiceName(serviceName);
}}
name="data-source-form"
layout="vertical"
validateTrigger="onBlur"
>
{selectedModule?.id === useCases.APM.id && (
<>
<Form.Item
name="serviceName"
label="Service Name"
style={{ width: 300 }}
rules={[{ required: true, message: 'Please enter service name' }]}
validateTrigger="onBlur"
>
<Input autoFocus />
</Form.Item>
{enableFrameworks && (
<div className="framework-selector">
{enableFrameworks && (
<div className="framework-selector">
<Form.Item
label="Select Framework"
name="selectFramework"
rules={[{ required: true, message: 'Please select framework' }]}
>
<Select
value={selectedFramework}
getPopupContainer={popupContainer}
style={{ width: 300 }}
placeholder="Select Framework"
onChange={(value): void => updateSelectedFramework(value)}
options={supportedframeworks}
/>
</Form.Item>
</div>
)}
</>
)}
<div className="request-entity-container">
<Typography.Text>
Cannot find what youre looking for? Request a data source
</Typography.Text>
<div className="form-section">
<Space.Compact style={{ width: '100%' }}>
<Form.Item
label="Select Framework"
name="selectFramework"
rules={[{ required: true, message: 'Please select framework' }]}
name="requestedDataSourceName"
style={{ width: 300, marginBottom: 0 }}
>
<Select
value={selectedFramework}
getPopupContainer={popupContainer}
style={{ minWidth: 120 }}
placeholder="Select Framework"
onChange={(value): void => updateSelectedFramework(value)}
options={supportedframeworks}
/>
<Input placeholder="Enter data source name..." />
</Form.Item>
</div>
)}
</Form>
</div>
<Button
className="periscope-btn primary"
icon={
isSubmittingRequestForDataSource ? (
<LoadingOutlined />
) : (
<Check size={12} />
)
}
type="primary"
onClick={handleRequestDataSourceSubmit}
disabled={
isSubmittingRequestForDataSource ||
!requestedDataSourceName ||
requestedDataSourceName?.trim().length === 0
}
>
Submit
</Button>
</Space.Compact>
</div>
</div>
</Form>
</div>
)}
</div>
</div>
);
}

View File

@ -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<void> => {
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 (
<>
<Form
initialValues={{}}
form={form}
name="environment-form"
layout="vertical"
>
<Typography.Text className="environment-title">
<span className="required-symbol">*</span> Select Environment
</Typography.Text>
@ -80,11 +147,47 @@ export default function EnvironmentDetails(): JSX.Element {
})}
</div>
<div className="request-entity-container">
<Typography.Text>
Cannot find what youre looking for? Request a data source
</Typography.Text>
<div className="form-section">
<Space.Compact style={{ width: '100%' }}>
<Form.Item
name="requestedEnvironmentName"
style={{ width: 300, marginBottom: 0 }}
>
<Input placeholder="Enter environment name..." />
</Form.Item>
<Button
className="periscope-btn primary"
icon={
isSubmittingRequestForEnvironment ? (
<LoadingOutlined />
) : (
<Check size={12} />
)
}
type="primary"
onClick={handleRequestedEnvironmentSubmit}
disabled={
isSubmittingRequestForEnvironment ||
!requestedEnvironmentName ||
requestedEnvironmentName?.trim().length === 0
}
>
Submit
</Button>
</Space.Compact>
</div>
</div>
{errorDetails && (
<div className="error-container">
<Typography.Text type="danger"> {errorDetails} </Typography.Text>
</div>
)}
</>
</Form>
);
}

View File

@ -30,6 +30,11 @@
background-color: #4566d6;
box-shadow: 0 2px 0 rgba(62, 86, 245, 0.09);
}
&:disabled {
opacity: 0.5;
}
}
.periscope-tab {

View File

@ -0,0 +1,9 @@
export interface EventSuccessPayloadProps {
status: string;
data: string;
}
export interface EventRequestPayloadProps {
eventName: string;
attributes: Record<string, unknown>;
}

View File

@ -6,6 +6,7 @@ declare global {
interface Window {
store: Store;
clarity: ClarityType<string>;
Intercom: any;
analytics: Record<string, any>;
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose;
}