Skip to content

Commit

Permalink
chore: added premium tags datasources for knowing users request to ad…
Browse files Browse the repository at this point in the history
…d new integrations (#38110)
  • Loading branch information
AmanAgarwal041 authored Dec 19, 2024
1 parent 1ade47d commit e22dbd1
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 8 deletions.
27 changes: 26 additions & 1 deletion app/client/src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2576,9 +2576,34 @@ export const REQUEST_NEW_INTEGRATIONS = {
REQUEST_MODAL_EMAIL: {
LABEL: () => "Email",
DESCRIPTION: () =>
"Appsmith might use this email exclusively to follow up on your integration request.",
"Appsmith will use this email exclusively to follow up on your integration request.",
NAME: "email",
ERROR: () => "Please enter email",
},
SUCCESS_TOAST_MESSAGE: () => "Thank you! We are looking into your request.",
};

export const PREMIUM_DATASOURCES = {
RELEVANT_EMAIL_DESCRIPTION: () =>
"Unblock advanced integrations. Let our team guide you in selecting the plan that fits your needs. Schedule a call now to see how Appsmith can transform your workflows!",
NON_RELEVANT_EMAIL_DESCRIPTION: () =>
"Unblock advanced integrations. Let our team guide you in selecting the plan that fits your needs. Give us your email and the Appsmith team will reach out to you soon.",
LEARN_MORE: () => "Learn more about Premium",
SCHEDULE_CALL: () => "Schedule a call",
SUBMIT: () => "Submit",
SUCCESS_TOAST_MESSAGE: () =>
"Thank you! The Appsmith Team will contact you shortly.",
FORM_EMAIL: {
LABEL: () => "Email",
DESCRIPTION: () =>
"Appsmith will use this email to follow up on your integration interest.",
NAME: "email",
ERROR: () => "Please enter email",
},
PREMIUM_TAG: () => "Premium",
SOON_TAG: () => "Soon",
COMING_SOON_SUFFIX: () => "Coming soon",
COMING_SOON_DESCRIPTION: () =>
"The Appsmith Team is actively working on it. We’ll let you know when this integration is live. ",
NOTIFY_ME: () => "Notify me",
};
2 changes: 2 additions & 0 deletions app/client/src/ce/entities/FeatureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const FEATURE_FLAG = {
"release_table_html_column_type_enabled",
release_gs_all_sheets_options_enabled:
"release_gs_all_sheets_options_enabled",
ab_premium_datasources_view_enabled: "ab_premium_datasources_view_enabled",
} as const;

export type FeatureFlag = keyof typeof FEATURE_FLAG;
Expand Down Expand Up @@ -89,6 +90,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
release_evaluation_scope_cache: false,
release_table_html_column_type_enabled: false,
release_gs_all_sheets_options_enabled: false,
ab_premium_datasources_view_enabled: false,
};

export const AB_TESTING_EVENT_KEYS = {
Expand Down
12 changes: 11 additions & 1 deletion app/client/src/ce/utils/analyticsUtilTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ export type EventName =
| "MALFORMED_USAGE_PULSE"
| "REQUEST_INTEGRATION_CTA"
| "REQUEST_INTEGRATION_SUBMITTED"
| "TABLE_WIDGET_V2_HTML_CELL_USAGE";
| "TABLE_WIDGET_V2_HTML_CELL_USAGE"
| PREMIUM_DATASOURCES_EVENTS;

type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS =
| "TEMPLATE_DROPDOWN_CLICK"
Expand Down Expand Up @@ -469,3 +470,12 @@ export type CUSTOM_WIDGET_EVENTS =
| "CUSTOM_WIDGET_BUILDER_DEBUGGER_VISIBILITY_CHANGED"
| "CUSTOM_WIDGET_API_TRIGGER_EVENT"
| "CUSTOM_WIDGET_API_UPDATE_MODEL";

export type PREMIUM_DATASOURCES_EVENTS =
| "PREMIUM_INTEGRATION_CTA"
| "PREMIUM_MODAL_RELEVANT_LEARN_MORE"
| "PREMIUM_MODAL_NOT_RELEVANT_LEARN_MORE"
| "PREMIUM_MODAL_RELEVANT_SCHEDULE_CALL"
| "PREMIUM_MODAL_NOT_RELEVANT_SUBMIT"
| "SOON_INTEGRATION_CTA"
| "SOON_NOTIFY_REQUEST";
21 changes: 21 additions & 0 deletions app/client/src/constants/PremiumDatasourcesConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getAssetUrl } from "ee/utils/airgapHelpers";
import { ASSETS_CDN_URL } from "./ThirdPartyConstants";

interface PremiumIntegration {
name: string;
icon: string;
}

export const PREMIUM_INTEGRATIONS: PremiumIntegration[] = [
{
name: "Zendesk",
icon: getAssetUrl(`${ASSETS_CDN_URL}/zendesk-icon.png`),
},
{
name: "Salesforce",
icon: getAssetUrl(`${ASSETS_CDN_URL}/salesforce-icon.png`),
},
];

export const PREMIUM_INTEGRATION_CONTACT_FORM =
"PREMIUM_INTEGRATION_CONTACT_FORM";
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import AIDataSources from "./AIDataSources";
import Debugger from "../DataSourceEditor/Debugger";
import { isPluginActionCreating } from "PluginActionEditor/store";
import RequestNewIntegration from "./RequestNewIntegration";
import PremiumDatasources from "pages/Editor/IntegrationEditor/PremiumDatasources";

const NewIntegrationsContainer = styled.div`
${thinScrollbar};
Expand Down Expand Up @@ -130,6 +131,7 @@ function CreateNewDatasource({
active,
isCreating,
isOnboardingScreen,
isPremiumDatasourcesViewEnabled,
pageId,
showMostPopularPlugins,
showUnsupportedPluginDialog, // TODO: Fix this the next time the file is edited
Expand Down Expand Up @@ -170,7 +172,11 @@ function CreateNewDatasource({
parentEntityType={parentEntityType}
showMostPopularPlugins={showMostPopularPlugins}
showUnsupportedPluginDialog={showUnsupportedPluginDialog}
/>
>
{showMostPopularPlugins && isPremiumDatasourcesViewEnabled && (
<PremiumDatasources />
)}
</NewQueryScreen>
</div>
);
}
Expand Down Expand Up @@ -252,6 +258,7 @@ interface CreateNewDatasourceScreenProps {
pageId: string;
isOnboardingScreen?: boolean;
isRequestNewIntegrationEnabled: boolean;
isPremiumDatasourcesViewEnabled: boolean;
}

interface CreateNewDatasourceScreenState {
Expand Down Expand Up @@ -283,6 +290,7 @@ class CreateNewDatasourceTab extends React.Component<
dataSources,
isCreating,
isOnboardingScreen,
isPremiumDatasourcesViewEnabled,
isRequestNewIntegrationEnabled,
pageId,
showDebugger,
Expand Down Expand Up @@ -313,6 +321,7 @@ class CreateNewDatasourceTab extends React.Component<
active={false}
isCreating={isCreating}
isOnboardingScreen={!!isOnboardingScreen}
isPremiumDatasourcesViewEnabled={isPremiumDatasourcesViewEnabled}
location={location}
pageId={pageId}
showMostPopularPlugins
Expand Down Expand Up @@ -386,6 +395,9 @@ const mapStateToProps = (state: AppState) => {
const isRequestNewIntegrationEnabled =
!!featureFlags?.ab_request_new_integration_enabled;

const isPremiumDatasourcesViewEnabled =
!!featureFlags?.ab_premium_datasources_view_enabled;

return {
dataSources: getDatasources(state),
mockDatasources: getMockDatasources(state),
Expand All @@ -395,6 +407,7 @@ const mapStateToProps = (state: AppState) => {
showDebugger,
pageId,
isRequestNewIntegrationEnabled,
isPremiumDatasourcesViewEnabled,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { type ReactNode } from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { initialize } from "redux-form";
Expand Down Expand Up @@ -132,6 +132,7 @@ interface DatasourceHomeScreenProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
showUnsupportedPluginDialog: (callback: any) => void;
isAirgappedInstance?: boolean;
children?: ReactNode;
}

interface ReduxDispatchProps {
Expand Down Expand Up @@ -294,6 +295,7 @@ class DatasourceHomeScreen extends React.Component<Props> {
</DatasourceCard>
);
})}
{this.props.children}
</DatasourceCardsContainer>
</DatasourceHomePage>
);
Expand Down
4 changes: 3 additions & 1 deletion app/client/src/pages/Editor/IntegrationEditor/NewQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class QueryHomeScreen extends React.Component<QueryHomeScreenProps> {
parentEntityType={parentEntityType}
showMostPopularPlugins={showMostPopularPlugins}
showUnsupportedPluginDialog={showUnsupportedPluginDialog}
/>
>
{this.props.children}
</DataSourceHome>
</QueryHomePage>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { Button, Flex, ModalHeader, Text, toast } from "@appsmith/ads";
import { createMessage, PREMIUM_DATASOURCES } from "ee/constants/messages";
import type { AppState } from "ee/reducers";
import React, { useCallback } from "react";
import { connect, useSelector } from "react-redux";
import {
Field,
formValueSelector,
getFormSyncErrors,
reduxForm,
type FormErrors,
type InjectedFormProps,
} from "redux-form";
import { getCurrentUser } from "selectors/usersSelectors";
import styled from "styled-components";
import { isEmail } from "utils/formhelpers";
import ReduxFormTextField from "components/utils/ReduxFormTextField";
import { PRICING_PAGE_URL } from "constants/ThirdPartyConstants";
import { getAppsmithConfigs } from "ee/configs";
import { getInstanceId, isFreePlan } from "ee/selectors/tenantSelectors";
import { pricingPageUrlSource } from "ee/utils/licenseHelpers";
import { RampFeature, RampSection } from "utils/ProductRamps/RampsControlList";
import {
getContactFormModalDescription,
getContactFormModalTitle,
getContactFormSubmitButtonText,
handleLearnMoreClick,
handleSubmitEvent,
shouldLearnMoreButtonBeVisible,
} from "utils/PremiumDatasourcesHelpers";
import { PREMIUM_INTEGRATION_CONTACT_FORM } from "constants/PremiumDatasourcesConstants";

const FormWrapper = styled.form`
display: flex;
flex-direction: column;
gap: var(--ads-spaces-7);
`;

const PremiumDatasourceContactForm = (
props: PremiumDatasourceContactFormProps,
) => {
const instanceId = useSelector(getInstanceId);
const appsmithConfigs = getAppsmithConfigs();
const isFreePlanInstance = useSelector(isFreePlan);

const redirectPricingURL = PRICING_PAGE_URL(
appsmithConfigs.pricingUrl,
pricingPageUrlSource,
instanceId,
RampFeature.PremiumDatasources,
RampSection.PremiumDatasourcesContactModal,
);

const onSubmit = () => {
submitEvent();
toast.show(createMessage(PREMIUM_DATASOURCES.SUCCESS_TOAST_MESSAGE), {
kind: "success",
});
props.closeModal();
};

const onClickLearnMore = useCallback(() => {
handleLearnMoreClick(
props.integrationName,
props.email || "",
redirectPricingURL,
);
}, [redirectPricingURL, props.email, props.integrationName]);

const submitEvent = useCallback(() => {
handleSubmitEvent(
props.integrationName,
props.email || "",
!isFreePlanInstance,
);
}, [props.email, props.integrationName, isFreePlanInstance]);

return (
<>
<ModalHeader>
{getContactFormModalTitle(props.integrationName, !isFreePlanInstance)}
</ModalHeader>
<FormWrapper onSubmit={props.handleSubmit(onSubmit)}>
<Text renderAs="p">
{getContactFormModalDescription(
props.email || "",
!isFreePlanInstance,
)}
</Text>
<Field
component={ReduxFormTextField}
description={createMessage(
PREMIUM_DATASOURCES.FORM_EMAIL.DESCRIPTION,
)}
label={createMessage(PREMIUM_DATASOURCES.FORM_EMAIL.LABEL)}
name={PREMIUM_DATASOURCES.FORM_EMAIL.NAME}
size="md"
type="email"
/>
<Flex gap="spaces-7" justifyContent="flex-end" marginTop="spaces-3">
{shouldLearnMoreButtonBeVisible(!isFreePlanInstance) && (
<Button
aria-label="Learn more"
kind="secondary"
onClick={onClickLearnMore}
size="md"
>
{createMessage(PREMIUM_DATASOURCES.LEARN_MORE)}
</Button>
)}
<Button isDisabled={props.invalid} size="md" type="submit">
{getContactFormSubmitButtonText(
props.email || "",
!isFreePlanInstance,
)}
</Button>
</Flex>
</FormWrapper>
</>
);
};

const selector = formValueSelector(PREMIUM_INTEGRATION_CONTACT_FORM);

interface PremiumDatasourceContactFormValues {
email?: string;
}

type PremiumDatasourceContactFormProps = PremiumDatasourceContactFormValues & {
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
} & InjectedFormProps<
PremiumDatasourceContactFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
}
>;

const validate = (values: PremiumDatasourceContactFormValues) => {
const errors: Partial<PremiumDatasourceContactFormValues> = {};

if (!values.email || !isEmail(values.email)) {
errors.email = createMessage(PREMIUM_DATASOURCES.FORM_EMAIL.ERROR);
}

return errors;
};

export default connect((state: AppState) => {
const currentUser = getCurrentUser(state);

return {
email: selector(state, "email"),
initialValues: {
email: currentUser?.email,
},
formSyncErrors: getFormSyncErrors(PREMIUM_INTEGRATION_CONTACT_FORM)(state),
};
}, null)(
reduxForm<
PremiumDatasourceContactFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
integrationName: string;
}
>({
validate,
form: PREMIUM_INTEGRATION_CONTACT_FORM,
enableReinitialize: true,
})(PremiumDatasourceContactForm),
);
Loading

0 comments on commit e22dbd1

Please sign in to comment.