-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🪟 🧪 [Experiment] Show source selector on signup form (#18468)
* 🪟 🧪 [Experiment] Show source selector on signup form Demo: https://www.loom.com/share/f676522a48184a48adfb461232937f5e * fetch directly from cloud catalog * remove cloud catalog.json * wrap onChangeServiceType on useCallback * cleanup * filter out hidden cloud connectors * Connections Flow * fix import
- Loading branch information
1 parent
5846c65
commit 99195c0
Showing
12 changed files
with
1,916 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { useLocation } from "react-router-dom"; | ||
|
||
interface ILocationState<T> extends Omit<Location, "state"> { | ||
state: T; | ||
} | ||
|
||
export const useLocationState = <T>(): T => { | ||
const location = useLocation() as unknown as ILocationState<T>; | ||
return location.state; | ||
}; |
197 changes: 197 additions & 0 deletions
197
...p/src/packages/cloud/components/experiments/SignupSourceDropdown/SignupSourceDropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import { faPlus } from "@fortawesome/free-solid-svg-icons"; | ||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||
import React, { useCallback, useMemo, useState } from "react"; | ||
import { FormattedMessage, useIntl } from "react-intl"; | ||
import { components } from "react-select"; | ||
import { MenuListProps } from "react-select"; | ||
|
||
import { GAIcon } from "components/icons/GAIcon"; | ||
import { ControlLabels } from "components/LabeledControl"; | ||
import { | ||
DropDown, | ||
DropDownOptionDataItem, | ||
DropDownOptionProps, | ||
OptionView, | ||
SingleValueIcon, | ||
SingleValueProps, | ||
SingleValueView, | ||
} from "components/ui/DropDown"; | ||
import { Text } from "components/ui/Text"; | ||
|
||
import { ReleaseStage } from "core/request/AirbyteClient"; | ||
import { useModalService } from "hooks/services/Modal"; | ||
import RequestConnectorModal from "views/Connector/RequestConnectorModal"; | ||
import styles from "views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.module.scss"; | ||
import { useAnalyticsTrackFunctions } from "views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/useAnalyticsTrackFunctions"; | ||
import { WarningMessage } from "views/Connector/ServiceForm/components/WarningMessage"; | ||
|
||
import { useGetSourceDefinitions } from "./useGetSourceDefinitions"; | ||
import { getSortedDropdownData } from "./utils"; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
type MenuWithRequestButtonProps = MenuListProps<DropDownOptionDataItem, false> & { selectProps: any }; | ||
|
||
const ConnectorList: React.FC<React.PropsWithChildren<MenuWithRequestButtonProps>> = ({ children, ...props }) => ( | ||
<> | ||
<components.MenuList {...props}>{children}</components.MenuList> | ||
<div className={styles.connectorListFooter}> | ||
<button | ||
className={styles.requestNewConnectorBtn} | ||
onClick={() => props.selectProps.selectProps.onOpenRequestConnectorModal(props.selectProps.inputValue)} | ||
> | ||
<FontAwesomeIcon icon={faPlus} /> | ||
<FormattedMessage id="connector.requestConnectorBlock" /> | ||
</button> | ||
</div> | ||
</> | ||
); | ||
|
||
const StageLabel: React.FC<{ releaseStage?: ReleaseStage }> = ({ releaseStage }) => { | ||
if (!releaseStage) { | ||
return null; | ||
} | ||
|
||
if (releaseStage === ReleaseStage.generally_available) { | ||
return <GAIcon />; | ||
} | ||
|
||
return ( | ||
<div className={styles.stageLabel}> | ||
<FormattedMessage id={`connector.releaseStage.${releaseStage}`} defaultMessage={releaseStage} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const Option: React.FC<DropDownOptionProps> = (props) => { | ||
return ( | ||
<components.Option {...props}> | ||
<OptionView | ||
data-testid={props.data.label} | ||
isSelected={props.isSelected} | ||
isDisabled={props.isDisabled} | ||
isFocused={props.isFocused} | ||
> | ||
<div className={styles.connectorName}> | ||
{props.data.img || null} | ||
<Text size="lg">{props.label}</Text> | ||
</div> | ||
<StageLabel releaseStage={props.data.releaseStage} /> | ||
</OptionView> | ||
</components.Option> | ||
); | ||
}; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const SingleValue: React.FC<SingleValueProps<any>> = (props) => { | ||
return ( | ||
<SingleValueView> | ||
{props.data.img && <SingleValueIcon>{props.data.img}</SingleValueIcon>} | ||
<div> | ||
<components.SingleValue className={styles.singleValueContent} {...props}> | ||
{props.data.label} | ||
<StageLabel releaseStage={props.data.releaseStage} /> | ||
</components.SingleValue> | ||
</div> | ||
</SingleValueView> | ||
); | ||
}; | ||
|
||
interface SignupSourceDropdownProps { | ||
disabled?: boolean; | ||
email: string; | ||
} | ||
|
||
export const SignupSourceDropdown: React.FC<SignupSourceDropdownProps> = ({ disabled, email }) => { | ||
const { formatMessage } = useIntl(); | ||
const { openModal, closeModal } = useModalService(); | ||
const { trackMenuOpen, trackNoOptionMessage, trackConnectorSelection } = useAnalyticsTrackFunctions("source"); | ||
|
||
const { data: availableSources } = useGetSourceDefinitions(); | ||
|
||
const [sourceDefinitionId, setSourceDefinitionId] = useState<string>(""); | ||
|
||
const onChangeServiceType = useCallback((sourceDefinitionId: string) => { | ||
setSourceDefinitionId(sourceDefinitionId); | ||
localStorage.setItem("exp-signup-selected-source-definition-id", sourceDefinitionId); | ||
}, []); | ||
|
||
const sortedDropDownData = useMemo(() => getSortedDropdownData(availableSources ?? []), [availableSources]); | ||
|
||
const getNoOptionsMessage = useCallback( | ||
({ inputValue }: { inputValue: string }) => { | ||
trackNoOptionMessage(inputValue); | ||
return formatMessage({ id: "form.noConnectorFound" }); | ||
}, | ||
[formatMessage, trackNoOptionMessage] | ||
); | ||
|
||
const selectedService = React.useMemo( | ||
() => sortedDropDownData.find((s) => s.value === sourceDefinitionId), | ||
[sourceDefinitionId, sortedDropDownData] | ||
); | ||
|
||
const handleSelect = useCallback( | ||
(item: DropDownOptionDataItem | null) => { | ||
if (item && onChangeServiceType) { | ||
onChangeServiceType(item.value); | ||
trackConnectorSelection(item.value, item.label || ""); | ||
} | ||
}, | ||
[onChangeServiceType, trackConnectorSelection] | ||
); | ||
|
||
const selectProps = useMemo( | ||
() => ({ | ||
onOpenRequestConnectorModal: (input: string) => | ||
openModal({ | ||
title: formatMessage({ id: "connector.requestConnector" }), | ||
content: () => ( | ||
<RequestConnectorModal | ||
connectorType="source" | ||
workspaceEmail={email} | ||
searchedConnectorName={input} | ||
onClose={closeModal} | ||
/> | ||
), | ||
}), | ||
}), | ||
[closeModal, formatMessage, openModal, email] | ||
); | ||
|
||
if (!Boolean(sortedDropDownData.length)) { | ||
return null; | ||
} | ||
return ( | ||
<> | ||
<ControlLabels | ||
label={formatMessage({ | ||
id: "login.sourceSelector", | ||
})} | ||
> | ||
<DropDown | ||
value={sourceDefinitionId} | ||
components={{ | ||
MenuList: ConnectorList, | ||
Option, | ||
SingleValue, | ||
}} | ||
selectProps={selectProps} | ||
isDisabled={disabled} | ||
isSearchable | ||
placeholder={formatMessage({ | ||
id: "form.selectConnector", | ||
})} | ||
options={sortedDropDownData} | ||
onChange={handleSelect} | ||
onMenuOpen={trackMenuOpen} | ||
noOptionsMessage={getNoOptionsMessage} | ||
data-testid="serviceType" | ||
/> | ||
</ControlLabels> | ||
{selectedService && | ||
(selectedService.releaseStage === ReleaseStage.alpha || selectedService.releaseStage === ReleaseStage.beta) && ( | ||
<WarningMessage stage={selectedService.releaseStage} /> | ||
)} | ||
</> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
airbyte-webapp/src/packages/cloud/components/experiments/SignupSourceDropdown/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { SignupSourceDropdown } from "./SignupSourceDropdown"; |
1,596 changes: 1,596 additions & 0 deletions
1,596
...app/src/packages/cloud/components/experiments/SignupSourceDropdown/sourceDefinitions.json
Large diffs are not rendered by default.
Oops, something went wrong.
34 changes: 34 additions & 0 deletions
34
...rc/packages/cloud/components/experiments/SignupSourceDropdown/useGetSourceDefinitions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { useQuery } from "react-query"; | ||
|
||
import { getExcludedConnectorIds } from "core/domain/connector/constants"; | ||
import { DestinationDefinitionRead, SourceDefinitionRead } from "core/request/AirbyteClient"; | ||
|
||
import availableSourceDefinitions from "./sourceDefinitions.json"; | ||
|
||
interface Catalog { | ||
destinations: DestinationDefinitionRead[]; | ||
sources: SourceDefinitionRead[]; | ||
} | ||
const fetchCatalog = async (): Promise<Catalog> => { | ||
const path = "https://storage.googleapis.com/prod-airbyte-cloud-connector-metadata-service/cloud_catalog.json"; | ||
const response = await fetch(path); | ||
return response.json(); | ||
}; | ||
|
||
export const useGetSourceDefinitions = () => { | ||
return useQuery<Catalog, Error, Catalog["sources"]>("cloud_catalog", fetchCatalog, { | ||
select: (data) => { | ||
return data.sources | ||
.filter(() => getExcludedConnectorIds("")) | ||
.map((source) => { | ||
const icon = availableSourceDefinitions.sourceDefinitions.find( | ||
(src) => src.sourceDefinitionId === source.sourceDefinitionId | ||
)?.icon; | ||
return { | ||
...source, | ||
icon, | ||
}; | ||
}); | ||
}, | ||
}); | ||
}; |
43 changes: 43 additions & 0 deletions
43
airbyte-webapp/src/packages/cloud/components/experiments/SignupSourceDropdown/utils.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { ConnectorIcon } from "components/common/ConnectorIcon"; | ||
|
||
import { Connector } from "core/domain/connector"; | ||
import { ReleaseStage, SourceDefinitionRead } from "core/request/AirbyteClient"; | ||
import { naturalComparator } from "utils/objects"; | ||
|
||
/** | ||
* Returns the order for a specific release stage label. This will define | ||
* in what order the different release stages are shown inside the select. | ||
* They will be shown in an increasing order (i.e. 0 on top) | ||
*/ | ||
const getOrderForReleaseStage = (stage?: ReleaseStage): number => { | ||
switch (stage) { | ||
case ReleaseStage.beta: | ||
return 1; | ||
case ReleaseStage.alpha: | ||
return 2; | ||
default: | ||
return 0; | ||
} | ||
}; | ||
interface ServiceDropdownOption { | ||
label: string; | ||
value: string; | ||
img: JSX.Element; | ||
releaseStage: ReleaseStage | undefined; | ||
} | ||
const transformConnectorDefinitionToDropdownOption = (item: SourceDefinitionRead): ServiceDropdownOption => ({ | ||
label: item.name, | ||
value: Connector.id(item), | ||
img: <ConnectorIcon icon={item.icon} />, | ||
releaseStage: item.releaseStage, | ||
}); | ||
|
||
const sortByReleaseStage = (a: ServiceDropdownOption, b: ServiceDropdownOption) => { | ||
if (a.releaseStage !== b.releaseStage) { | ||
return getOrderForReleaseStage(a.releaseStage) - getOrderForReleaseStage(b.releaseStage); | ||
} | ||
return naturalComparator(a.label, b.label); | ||
}; | ||
|
||
export const getSortedDropdownData = (availableConnectorDefinitions: SourceDefinitionRead[]): ServiceDropdownOption[] => | ||
availableConnectorDefinitions.map(transformConnectorDefinitionToDropdownOption).sort(sortByReleaseStage); |
1 change: 1 addition & 0 deletions
1
airbyte-webapp/src/packages/cloud/components/experiments/constants.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const EXP_SOURCE_SIGNUP_SELECTOR = "exp-signup-selected-source-definition-id"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.