feat: support request data source and improve layout (#7485)

* feat: support request data source and improve layout

* feat: update config

* feat: update config with related keywords

* update config

---------

Co-authored-by: makeavish <makeavish786@gmail.com>
This commit is contained in:
Yunus M 2025-04-02 11:59:53 +05:30 committed by GitHub
parent 597752a4bc
commit 5ef3b8ee3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1451 additions and 92 deletions

View File

@ -15,11 +15,12 @@ import {
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport'; import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import history from 'lib/history'; import history from 'lib/history';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { ArrowRight, X } from 'lucide-react'; import { CheckIcon, Goal, UserPlus, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
import React, { useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import OnboardingIngestionDetails from '../IngestionDetails/IngestionDetails'; import OnboardingIngestionDetails from '../IngestionDetails/IngestionDetails';
import InviteTeamMembers from '../InviteTeamMembers/InviteTeamMembers'; import InviteTeamMembers from '../InviteTeamMembers/InviteTeamMembers';
@ -68,6 +69,7 @@ interface Entity {
}; };
}; };
tags: string[]; tags: string[];
relatedSearchKeywords?: string[];
link?: string; link?: string;
} }
@ -99,8 +101,11 @@ const ONBOARDING_V3_ANALYTICS_EVENTS_MAP = {
GET_EXPERT_ASSISTANCE_BUTTON_CLICKED: 'Get expert assistance clicked', GET_EXPERT_ASSISTANCE_BUTTON_CLICKED: 'Get expert assistance clicked',
INVITE_TEAM_MEMBER_BUTTON_CLICKED: 'Invite team member clicked', INVITE_TEAM_MEMBER_BUTTON_CLICKED: 'Invite team member clicked',
CLOSE_ONBOARDING_CLICKED: 'Close onboarding clicked', CLOSE_ONBOARDING_CLICKED: 'Close onboarding clicked',
DATA_SOURCE_REQUESTED: 'Datasource requested',
DATA_SOURCE_SEARCHED: 'Searched',
}; };
// eslint-disable-next-line sonarjs/cognitive-complexity
function OnboardingAddDataSource(): JSX.Element { function OnboardingAddDataSource(): JSX.Element {
const [groupedDataSources, setGroupedDataSources] = useState<{ const [groupedDataSources, setGroupedDataSources] = useState<{
[tag: string]: Entity[]; [tag: string]: Entity[];
@ -110,6 +115,8 @@ function OnboardingAddDataSource(): JSX.Element {
const [setupStepItems, setSetupStepItems] = useState(setupStepItemsBase); const [setupStepItems, setSetupStepItems] = useState(setupStepItemsBase);
const [searchQuery, setSearchQuery] = useState<string>('');
const question2Ref = useRef<HTMLDivElement | null>(null); const question2Ref = useRef<HTMLDivElement | null>(null);
const question3Ref = useRef<HTMLDivElement | null>(null); const question3Ref = useRef<HTMLDivElement | null>(null);
const configureProdRef = useRef<HTMLDivElement | null>(null); const configureProdRef = useRef<HTMLDivElement | null>(null);
@ -120,8 +127,15 @@ function OnboardingAddDataSource(): JSX.Element {
const [currentStep, setCurrentStep] = useState(1); const [currentStep, setCurrentStep] = useState(1);
const [dataSourceRequest, setDataSourceRequest] = useState<string>('');
const [hasMoreQuestions, setHasMoreQuestions] = useState<boolean>(true); const [hasMoreQuestions, setHasMoreQuestions] = useState<boolean>(true);
const [
showRequestDataSourceModal,
setShowRequestDataSourceModal,
] = useState<boolean>(false);
const [ const [
showInviteTeamMembersModal, showInviteTeamMembersModal,
setShowInviteTeamMembersModal, setShowInviteTeamMembersModal,
@ -145,6 +159,11 @@ function OnboardingAddDataSource(): JSX.Element {
const [selectedCategory, setSelectedCategory] = useState<string>('All'); const [selectedCategory, setSelectedCategory] = useState<string>('All');
const [
dataSourceRequestSubmitted,
setDataSourceRequestSubmitted,
] = useState<boolean>(false);
const handleScrollToStep = (ref: React.RefObject<HTMLDivElement>): void => { const handleScrollToStep = (ref: React.RefObject<HTMLDivElement>): void => {
setTimeout(() => { setTimeout(() => {
ref.current?.scrollIntoView({ ref.current?.scrollIntoView({
@ -286,8 +305,10 @@ function OnboardingAddDataSource(): JSX.Element {
setGroupedDataSources(groupedDataSources); setGroupedDataSources(groupedDataSources);
}, []); }, []);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>): void => { const debouncedUpdate = useDebouncedFn((query) => {
const query = e.target.value.toLowerCase(); setSearchQuery(query as string);
setDataSourceRequestSubmitted(false);
if (query === '') { if (query === '') {
setGroupedDataSources( setGroupedDataSources(
@ -298,15 +319,35 @@ function OnboardingAddDataSource(): JSX.Element {
const filteredDataSources = onboardingConfigWithLinks.filter( const filteredDataSources = onboardingConfigWithLinks.filter(
(dataSource) => (dataSource) =>
dataSource.label.toLowerCase().includes(query) || dataSource.label.toLowerCase().includes(query as string) ||
dataSource.tags.some((tag) => tag.toLowerCase().includes(query)), dataSource.tags.some((tag) =>
tag.toLowerCase().includes(query as string),
) ||
dataSource.relatedSearchKeywords?.some((keyword) =>
keyword?.toLowerCase().includes(query as string),
),
); );
setGroupedDataSources( setGroupedDataSources(
groupDataSourcesByTags(filteredDataSources as Entity[]), groupDataSourcesByTags(filteredDataSources as Entity[]),
); );
};
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SEARCHED}`,
{
searchedDataSource: query,
},
);
}, 300);
const handleSearch = useCallback(
(e: React.ChangeEvent<HTMLInputElement>): void => {
const query = e.target.value.trim().toLowerCase();
debouncedUpdate(query || '');
},
[debouncedUpdate],
);
const handleFilterByCategory = (category: string): void => { const handleFilterByCategory = (category: string): void => {
setSelectedDataSource(null); setSelectedDataSource(null);
setSelectedFramework(null); setSelectedFramework(null);
@ -409,6 +450,129 @@ function OnboardingAddDataSource(): JSX.Element {
setShowInviteTeamMembersModal(true); setShowInviteTeamMembersModal(true);
}; };
const handleSubmitDataSourceRequest = (): void => {
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
{
requestedDataSource: dataSourceRequest,
},
);
setShowRequestDataSourceModal(false);
setDataSourceRequestSubmitted(true);
};
const handleRequestDataSource = (): void => {
setShowRequestDataSourceModal(true);
};
const handleRaiseRequest = (): void => {
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
{
requestedDataSource: searchQuery,
},
);
setDataSourceRequestSubmitted(true);
};
const renderRequestDataSource = (): JSX.Element => {
const isSearchQueryEmpty = searchQuery.length === 0;
const isNoResultsFound = Object.keys(groupedDataSources).length === 0;
return (
<div className="request-data-source-container">
{!isNoResultsFound && (
<>
<Typography.Text>Cant find what youre looking for?</Typography.Text>
<svg
xmlns="http://www.w3.org/2000/svg"
width="279"
height="2"
viewBox="0 0 279 2"
fill="none"
>
<path
d="M0 1L279 1"
stroke="#7190F9"
strokeOpacity="0.2"
strokeDasharray="4 4"
/>
</svg>
{!dataSourceRequestSubmitted && (
<Button
type="default"
className="periscope-btn request-data-source-btn secondary"
icon={<Goal size={16} />}
onClick={handleRequestDataSource}
>
Request Data Source
</Button>
)}
{dataSourceRequestSubmitted && (
<Button
type="default"
className="periscope-btn request-data-source-btn success"
icon={<CheckIcon size={16} />}
>
Request raised
</Button>
)}
</>
)}
{isNoResultsFound && !isSearchQueryEmpty && (
<>
<Typography.Text>
Our team can help add{' '}
<span className="request-data-source-search-query">{searchQuery}</span>{' '}
support for you
</Typography.Text>
<svg
xmlns="http://www.w3.org/2000/svg"
width="279"
height="2"
viewBox="0 0 279 2"
fill="none"
>
<path
d="M0 1L279 1"
stroke="#7190F9"
strokeOpacity="0.2"
strokeDasharray="4 4"
/>
</svg>
{!dataSourceRequestSubmitted && (
<Button
type="default"
className="periscope-btn request-data-source-btn secondary"
icon={<Goal size={16} />}
onClick={handleRaiseRequest}
>
Raise request
</Button>
)}
{dataSourceRequestSubmitted && (
<Button
type="default"
className="periscope-btn request-data-source-btn success"
icon={<CheckIcon size={16} />}
>
Request raised
</Button>
)}
</>
)}
</div>
);
};
return ( return (
<div className="onboarding-v2"> <div className="onboarding-v2">
<Layout> <Layout>
@ -433,6 +597,15 @@ function OnboardingAddDataSource(): JSX.Element {
</div> </div>
<div className="header-right-section"> <div className="header-right-section">
<Button
type="default"
className="periscope-btn invite-teammate-btn outlined"
onClick={handleShowInviteTeamMembersModal}
icon={<UserPlus size={16} />}
>
Invite a teammate
</Button>
<LaunchChatSupport <LaunchChatSupport
attributes={{ attributes={{
dataSource: selectedDataSource?.dataSource, dataSource: selectedDataSource?.dataSource,
@ -442,7 +615,7 @@ function OnboardingAddDataSource(): JSX.Element {
}} }}
eventName={`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.GET_HELP_BUTTON_CLICKED}`} eventName={`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.GET_HELP_BUTTON_CLICKED}`}
message="" message=""
buttonText="Get Help" buttonText="Contact Support"
className="periscope-btn get-help-btn outlined" className="periscope-btn get-help-btn outlined"
/> />
</div> </div>
@ -461,7 +634,11 @@ function OnboardingAddDataSource(): JSX.Element {
</Header> </Header>
<div className="onboarding-product-setup-container"> <div className="onboarding-product-setup-container">
<div className="onboarding-product-setup-container_left-section"> <div
className={`onboarding-product-setup-container_left-section ${
currentStep === 1 ? 'step-id-1' : 'step-id-2'
}`}
>
<div className="perlian-bg" /> <div className="perlian-bg" />
{currentStep === 1 && ( {currentStep === 1 && (
@ -491,6 +668,7 @@ function OnboardingAddDataSource(): JSX.Element {
<div className="onboarding-data-source-search"> <div className="onboarding-data-source-search">
<Input <Input
placeholder="Search" placeholder="Search"
maxLength={20}
onChange={handleSearch} onChange={handleSearch}
addonAfter={<SearchOutlined />} addonAfter={<SearchOutlined />}
/> />
@ -525,6 +703,14 @@ function OnboardingAddDataSource(): JSX.Element {
</div> </div>
</div> </div>
))} ))}
{Object.keys(groupedDataSources).length === 0 && (
<div className="no-results-found-container">
<Typography.Text>No results for {searchQuery} :/</Typography.Text>
</div>
)}
{!selectedDataSource && renderRequestDataSource()}
</div> </div>
<div className="data-source-categories-filter-container"> <div className="data-source-categories-filter-container">
@ -534,33 +720,66 @@ function OnboardingAddDataSource(): JSX.Element {
Filters{' '} Filters{' '}
</Typography.Title> </Typography.Title>
<Typography.Title <div
level={5} key="all"
className={`onboarding-filters-item-title ${ className="onboarding-data-source-category-item"
selectedCategory === 'All' ? 'selected' : ''
}`}
onClick={(): void => handleFilterByCategory('All')} onClick={(): void => handleFilterByCategory('All')}
role="button"
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
handleFilterByCategory('All');
}
}}
> >
All ({onboardingConfigWithLinks.length}) <Typography.Title
</Typography.Title> level={5}
className={`onboarding-filters-item-title ${
selectedCategory === 'All' ? 'selected' : ''
}`}
>
All
</Typography.Title>
<div className="line-divider" />
<Typography.Text className="onboarding-filters-item-count">
{onboardingConfigWithLinks.length}
</Typography.Text>
</div>
{Object.keys(groupedDataSources).map((tag) => ( {Object.keys(groupedDataSources).map((tag) => (
<div key={tag} className="onboarding-data-source-category-item"> <div
key={tag}
className="onboarding-data-source-category-item"
onClick={(): void => handleFilterByCategory(tag)}
role="button"
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
handleFilterByCategory(tag);
}
}}
>
<Typography.Title <Typography.Title
level={5} level={5}
className={`onboarding-filters-item-title ${ className={`onboarding-filters-item-title ${
selectedCategory === tag ? 'selected' : '' selectedCategory === tag ? 'selected' : ''
}`} }`}
onClick={(): void => handleFilterByCategory(tag)}
> >
{tag} ({groupedDataSources[tag].length}) {tag}
</Typography.Title> </Typography.Title>
<div className="line-divider" />
<Typography.Text className="onboarding-filters-item-count">
{groupedDataSources[tag].length}
</Typography.Text>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
{selectedDataSource && {selectedDataSource &&
selectedDataSource?.question && selectedDataSource?.question &&
!isEmpty(selectedDataSource?.question) && ( !isEmpty(selectedDataSource?.question) && (
@ -615,7 +834,6 @@ function OnboardingAddDataSource(): JSX.Element {
)} )}
</div> </div>
)} )}
{selectedFramework && {selectedFramework &&
selectedFramework?.question && selectedFramework?.question &&
!isEmpty(selectedFramework?.question) && ( !isEmpty(selectedFramework?.question) && (
@ -659,7 +877,6 @@ function OnboardingAddDataSource(): JSX.Element {
)} )}
</div> </div>
)} )}
{!hasMoreQuestions && showConfigureProduct && ( {!hasMoreQuestions && showConfigureProduct && (
<div className="questionaire-footer" ref={configureProdRef}> <div className="questionaire-footer" ref={configureProdRef}>
<Button <Button
@ -767,39 +984,6 @@ function OnboardingAddDataSource(): JSX.Element {
</div> </div>
<div className="onboarding-product-setup-container_right-section"> <div className="onboarding-product-setup-container_right-section">
{currentStep === 1 && (
<div className="invite-user-section-content">
<Button
type="default"
shape="round"
className="invite-user-section-content-button"
onClick={handleShowInviteTeamMembersModal}
>
Invite a team member to help with this step
<ArrowRight size={14} />
</Button>
<div className="need-help-section-content-divider">Or</div>
<div className="need-help-section-content">
<Typography.Text>
Need help with setup? Upgrade now and get expert assistance.
</Typography.Text>
<LaunchChatSupport
attributes={{
dataSource: selectedDataSource?.dataSource,
framework: selectedFramework?.label,
environment: selectedEnvironment?.label,
currentPage: setupStepItems[currentStep]?.title || '',
}}
eventName={`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.GET_EXPERT_ASSISTANCE_BUTTON_CLICKED}`}
message=""
buttonText="Get Expert Assistance"
className="periscope-btn get-help-btn rounded-btn outlined"
/>
</div>
</div>
)}
{currentStep === 2 && <OnboardingIngestionDetails />} {currentStep === 2 && <OnboardingIngestionDetails />}
</div> </div>
</div> </div>
@ -824,6 +1008,46 @@ function OnboardingAddDataSource(): JSX.Element {
/> />
</div> </div>
</Modal> </Modal>
<Modal
className="request-data-source-modal"
title={<span className="title">Request Data Source</span>}
open={showRequestDataSourceModal}
closable
onCancel={(): void => setShowRequestDataSourceModal(false)}
width="640px"
footer={[
<Button
type="default"
className="periscope-btn outlined"
key="back"
onClick={(): void => setShowRequestDataSourceModal(false)}
icon={<X size={16} />}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
className="periscope-btn primary"
disabled={dataSourceRequest.length <= 0}
onClick={handleSubmitDataSourceRequest}
icon={<CheckIcon size={16} />}
>
Submit request
</Button>,
]}
destroyOnClose
>
<div className="request-data-source-modal-content">
<Typography.Text>Enter your request</Typography.Text>
<Input
placeholder="Eg: Kotlin"
className="request-data-source-modal-input"
onChange={(e): void => setDataSourceRequest(e.target.value)}
/>
</div>
</Modal>
</Layout> </Layout>
</div> </div>
); );

View File

@ -4,14 +4,15 @@
&__header { &__header {
background: rgba(11, 12, 14, 0.7); background: rgba(11, 12, 14, 0.7);
border-bottom: 1px solid var(--bg-slate-500);
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
padding: 16px 0px 0px 0px; padding: 12px 0px;
&--sticky { &--sticky {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0px 1rem; padding: 0px 1rem;
// margin-top: 16px; margin-top: 12px;
background: rgba(11, 12, 14, 0.7); background: rgba(11, 12, 14, 0.7);
backdrop-filter: blur(20px); backdrop-filter: blur(20px);
@ -323,7 +324,8 @@
} }
} }
.get-help-btn { .get-help-btn,
.invite-teammate-btn {
font-size: 11px; font-size: 11px;
padding: 6px 16px; padding: 6px 16px;
border: 1px solid var(--bg-slate-400) !important; border: 1px solid var(--bg-slate-400) !important;
@ -610,15 +612,61 @@
display: flex; display: flex;
.data-sources-container { .data-sources-container {
flex: 0 0 70%; flex: 0 0 80%;
max-width: 70%; max-width: 80%;
margin-right: 32px; margin-right: 32px;
} }
.data-source-categories-filter-container { .data-source-categories-filter-container {
flex: 0 0 30%; flex: 0 0 20%;
max-width: 30%; max-width: 20%;
.onboarding-data-source-category {
.onboarding-data-source-category-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
cursor: pointer;
}
.onboarding-filters-item-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0px !important;
}
.line-divider {
height: 1px;
margin: 0 16px;
flex-grow: 1;
border-top: 2px dotted var(--bg-slate-400);
}
.onboarding-filters-item-count {
color: var(--text-vanilla-400);
font-family: Inter;
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
background-color: var(--bg-ink-400);
border-radius: 4px;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
}
} }
} }
@ -643,8 +691,14 @@
max-width: 70%; max-width: 70%;
display: flex; display: flex;
flex-direction: column;
gap: 24px; gap: 24px;
&.step-id-1 {
flex: 0 0 90%;
max-width: 90%;
}
// border-right: 1px solid var(--Greyscale-Slate-400, #1d212d); // border-right: 1px solid var(--Greyscale-Slate-400, #1d212d);
.perlian-bg { .perlian-bg {
@ -678,7 +732,7 @@
&_right-section { &_right-section {
flex: 1; flex: 1;
max-width: 30%; max-width: 30%;
height: calc(100vh - 120px); height: calc(100vh - 130px);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -972,6 +1026,52 @@
} }
} }
.no-results-found-container {
.ant-typography {
color: rgba(192, 193, 195, 0.6);
font-family: Inter;
font-size: 11px;
font-style: normal;
line-height: 18px; /* 150% */
letter-spacing: 0.48px;
text-transform: uppercase;
}
}
.request-data-source-container {
display: flex;
align-items: center;
gap: 16px;
margin: 36px 0;
display: flex;
width: fit-content;
gap: 24px;
padding: 12px 12px 12px 16px;
border-radius: 6px;
background: rgba(171, 189, 255, 0.06);
.request-data-source-search-query {
border-radius: 2px;
border: 1px solid rgba(173, 127, 88, 0.1);
background: rgba(173, 127, 88, 0.1);
color: var(--Sienna-400, #bd9979);
font-size: 13px;
padding: 2px;
line-height: 20px; /* 142.857% */
}
.request-data-source-btn {
border-radius: 2px;
border: 1px solid var(--Slate-200, #2c3140);
background: var(--Ink-200, #23262e);
}
}
.onboarding-data-source-category-container { .onboarding-data-source-category-container {
flex: 1; flex: 1;
max-width: 30%; max-width: 30%;
@ -996,7 +1096,7 @@
} }
.onboarding-configure-container { .onboarding-configure-container {
height: calc(100vh - 120px); height: calc(100vh - 130px);
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -1070,7 +1170,8 @@
height: 18px; height: 18px;
} }
.invite-team-member-modal { .invite-team-member-modal,
.request-data-source-modal {
.ant-modal-content { .ant-modal-content {
background-color: var(--bg-ink-500); background-color: var(--bg-ink-500);
} }
@ -1079,9 +1180,26 @@
background-color: var(--bg-ink-500); background-color: var(--bg-ink-500);
} }
.invite-team-member-modal-content { .invite-team-member-modal-content,
.request-data-source-modal-content {
background-color: var(--bg-ink-500); background-color: var(--bg-ink-500);
} }
.request-data-source-modal-content {
padding: 12px 0;
}
.request-data-source-modal-input {
margin-top: 8px;
}
}
.request-data-source-modal {
.ant-modal-footer {
display: flex;
justify-content: flex-end;
align-items: center;
}
} }
.ingestion-setup-details-links { .ingestion-setup-details-links {
@ -1262,7 +1380,8 @@
} }
.onboarding-v2 { .onboarding-v2 {
.get-help-btn { .get-help-btn,
.invite-teammate-btn {
border: 1px solid var(--bg-vanilla-300) !important; border: 1px solid var(--bg-vanilla-300) !important;
color: var(--bg-ink-300) !important; color: var(--bg-ink-300) !important;

View File

@ -45,6 +45,13 @@
font-size: 11px; font-size: 11px;
font-weight: 400; font-weight: 400;
} }
&.success {
color: var(--bg-forest-400) !important;
border-radius: 2px;
border: 1px solid rgba(37, 225, 146, 0.1);
background: rgba(37, 225, 146, 0.1) !important;
}
} }
.periscope-tab { .periscope-tab {