Skip to content

Commit

Permalink
feat: requesting new integration ui to accept users preferences about…
Browse files Browse the repository at this point in the history
… the new integrations (appsmithorg#38012)
  • Loading branch information
AmanAgarwal041 authored Dec 9, 2024
1 parent c4c33b5 commit 11281c8
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 4 deletions.
28 changes: 28 additions & 0 deletions src/ce/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2544,3 +2544,31 @@ export const CUSTOM_WIDGET_BUILDER_TAB_TITLE = {
STYLE: () => "Style",
JS: () => "Javascript",
};

export const REQUEST_NEW_INTEGRATIONS = {
UNABLE_TO_FIND: () => "Can’t find what you are looking for?",
REQUEST_NEW_BUTTON: () => "Request a new integration",
REQUEST_BUTTON: () => "Request integration",
CANCEL_BUTTON: () => "Cancel",
REQUEST_MODAL_HEADING: () => "Request a new integration",
REQUEST_MODAL_INTEGRATION: {
LABEL: () => "Integration",
PLACEHOLDER: () => "E.g. Zendesk, JIRA, Slack, others",
NAME: "integration",
ERROR: () => "Please enter integration name",
},
REQUEST_MODAL_USECASE: {
LABEL: () => "Tell us more about your case",
PLACEHOLDER: () =>
"E.g. I want to create an app to manage my customers’ account.",
NAME: "useCase",
},
REQUEST_MODAL_EMAIL: {
LABEL: () => "Email",
DESCRIPTION: () =>
"Appsmith might 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.",
};
2 changes: 2 additions & 0 deletions src/ce/entities/FeatureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const FEATURE_FLAG = {
release_table_custom_loading_state_enabled:
"release_table_custom_loading_state_enabled",
release_custom_widget_ai_builder: "release_custom_widget_ai_builder",
ab_request_new_integration_enabled: "ab_request_new_integration_enabled",
} as const;

export type FeatureFlag = keyof typeof FEATURE_FLAG;
Expand Down Expand Up @@ -79,6 +80,7 @@ export const DEFAULT_FEATURE_FLAG_VALUE: FeatureFlags = {
release_ide_datasource_selector_enabled: false,
release_table_custom_loading_state_enabled: false,
release_custom_widget_ai_builder: false,
ab_request_new_integration_enabled: false,
};

export const AB_TESTING_EVENT_KEYS = {
Expand Down
4 changes: 3 additions & 1 deletion src/ce/utils/analyticsUtilTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ export type EventName =
| BUILDING_BLOCKS_EVENTS
| "VISIT_SELF_HOST_DOCS"
| "CANVAS_HOVER"
| "MALFORMED_USAGE_PULSE";
| "MALFORMED_USAGE_PULSE"
| "REQUEST_INTEGRATION_CTA"
| "REQUEST_INTEGRATION_SUBMITTED";

type HOMEPAGE_CREATE_APP_FROM_TEMPLATE_EVENTS =
| "TEMPLATE_DROPDOWN_CLICK"
Expand Down
9 changes: 6 additions & 3 deletions src/components/utils/ReduxFormTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,17 @@ const renderComponent = (
) : (
<Input
{...componentProps.input}
{...componentProps}
// type prop is omitted as textarea component doesn't support that
{...(componentProps.type === "textarea"
? omit(componentProps, "type")
: componentProps)}
errorMessage={
!componentProps.hideErrorMessage &&
showError &&
componentProps.meta.error
}
isDisabled={componentProps.disabled}
renderAs={"input"}
renderAs={componentProps.type === "textarea" ? "textarea" : "input"}
size="md"
value={value}
/>
Expand All @@ -48,7 +51,7 @@ export interface FormTextFieldProps {
name: string;
placeholder: string;
description?: string;
type?: "text" | "password" | "number" | "email" | "tel";
type?: "text" | "password" | "number" | "email" | "tel" | "textarea";
label?: React.ReactNode;
intent?: Intent;
disabled?: boolean;
Expand Down
8 changes: 8 additions & 0 deletions src/pages/Editor/IntegrationEditor/CreateNewDatasourceTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { useParentEntityInfo } from "ee/hooks/datasourceEditorHooks";
import AIDataSources from "./AIDataSources";
import Debugger from "../DataSourceEditor/Debugger";
import { isPluginActionCreating } from "PluginActionEditor/store";
import RequestNewIntegration from "./RequestNewIntegration";

const NewIntegrationsContainer = styled.div`
${thinScrollbar};
Expand Down Expand Up @@ -250,6 +251,7 @@ interface CreateNewDatasourceScreenProps {
showDebugger: boolean;
pageId: string;
isOnboardingScreen?: boolean;
isRequestNewIntegrationEnabled: boolean;
}

interface CreateNewDatasourceScreenState {
Expand Down Expand Up @@ -281,6 +283,7 @@ class CreateNewDatasourceTab extends React.Component<
dataSources,
isCreating,
isOnboardingScreen,
isRequestNewIntegrationEnabled,
pageId,
showDebugger,
} = this.props;
Expand Down Expand Up @@ -351,6 +354,7 @@ class CreateNewDatasourceTab extends React.Component<
</>
)}
</NewIntegrationsContainer>
{isRequestNewIntegrationEnabled && <RequestNewIntegration />}
{showDebugger && <Debugger />}
</>
);
Expand Down Expand Up @@ -379,6 +383,9 @@ const mapStateToProps = (state: AppState) => {
userWorkspacePermissions,
);

const isRequestNewIntegrationEnabled =
!!featureFlags?.ab_request_new_integration_enabled;

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

Expand Down
154 changes: 154 additions & 0 deletions src/pages/Editor/IntegrationEditor/RequestNewIntegration/form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { Button, Flex, toast } from "@appsmith/ads";
import { Close } from "@radix-ui/react-dialog";
import { createMessage, REQUEST_NEW_INTEGRATIONS } from "ee/constants/messages";
import type { AppState } from "ee/reducers";
import React from "react";
import { connect } 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 AnalyticsUtil from "ee/utils/AnalyticsUtil";

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

const RequestIntegrationForm = (props: RequestIntegrationFormProps) => {
const onSubmit = (values: RequestIntegrationFormValues) => {
AnalyticsUtil.logEvent("REQUEST_INTEGRATION_SUBMITTED", {
integration_name: values.integration,
use_case_description: values.useCase || "",
email: values.email,
});
toast.show(createMessage(REQUEST_NEW_INTEGRATIONS.SUCCESS_TOAST_MESSAGE), {
kind: "success",
});
props.closeModal();
};

return (
<FormWrapper onSubmit={props.handleSubmit(onSubmit)}>
<Field
component={ReduxFormTextField}
label={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_INTEGRATION.LABEL,
)}
name={REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_INTEGRATION.NAME}
placeholder={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_INTEGRATION.PLACEHOLDER,
)}
size="md"
/>
<Field
component={ReduxFormTextField}
label={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_USECASE.LABEL,
)}
name={REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_USECASE.NAME}
placeholder={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_USECASE.PLACEHOLDER,
)}
size="md"
type="textarea"
/>
<Field
component={ReduxFormTextField}
description={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_EMAIL.DESCRIPTION,
)}
label={createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_EMAIL.LABEL,
)}
name={REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_EMAIL.NAME}
size="md"
type="email"
/>
<Flex gap="spaces-7" justifyContent="flex-end" marginTop="spaces-3">
<Close>
<Button aria-label="Close" kind="secondary" size="md">
{createMessage(REQUEST_NEW_INTEGRATIONS.CANCEL_BUTTON)}
</Button>
</Close>
<Button isDisabled={props.invalid} size="md" type="submit">
{createMessage(REQUEST_NEW_INTEGRATIONS.REQUEST_BUTTON)}
</Button>
</Flex>
</FormWrapper>
);
};

const REQUEST_NEW_INTEGRATION_FORM_NAME = "REQUEST_NEW_INTEGRATION";

const selector = formValueSelector(REQUEST_NEW_INTEGRATION_FORM_NAME);

interface RequestIntegrationFormValues {
integration?: string;
email?: string;
useCase?: string;
}

type RequestIntegrationFormProps = RequestIntegrationFormValues & {
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
} & InjectedFormProps<
RequestIntegrationFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
}
>;

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

if (!values.integration) {
errors.integration = createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_INTEGRATION.ERROR,
);
}

if (!values.email || !isEmail(values.email)) {
errors.email = createMessage(
REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_EMAIL.ERROR,
);
}

return errors;
};

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

return {
integration: selector(state, "integration"),
email: selector(state, "email"),
useCase: selector(state, "useCase"),
initialValues: {
email: currentUser?.email,
},
formSyncErrors: getFormSyncErrors(REQUEST_NEW_INTEGRATION_FORM_NAME)(state),
};
}, null)(
reduxForm<
RequestIntegrationFormValues,
{
formSyncErrors?: FormErrors<string, string>;
closeModal: () => void;
}
>({
validate,
form: REQUEST_NEW_INTEGRATION_FORM_NAME,
enableReinitialize: true,
})(RequestIntegrationForm),
);
59 changes: 59 additions & 0 deletions src/pages/Editor/IntegrationEditor/RequestNewIntegration/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Button,
Flex,
Modal,
ModalContent,
ModalHeader,
ModalTrigger,
} from "@appsmith/ads";
import { createMessage, REQUEST_NEW_INTEGRATIONS } from "ee/constants/messages";
import React, { useState, type ReactNode } from "react";
import styled from "styled-components";
import Form from "./form";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";

const RequestNewIntegrationWrapper = styled(Flex)`
padding: var(--ads-spaces-7);
border-top: 1px solid var(--ads-v2-colors-content-surface-default-border);
position: sticky;
bottom: 0;
background: var(--ads-v2-color-bg);
`;

const ModalContentWrapper = styled(ModalContent)`
max-width: 518px;
`;

function RequestModal({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false);

return (
<Modal onOpenChange={setOpen} open={open}>
<ModalTrigger>{children}</ModalTrigger>
<ModalContentWrapper>
<ModalHeader>
{createMessage(REQUEST_NEW_INTEGRATIONS.REQUEST_MODAL_HEADING)}
</ModalHeader>
<Form closeModal={() => setOpen(false)} />
</ModalContentWrapper>
</Modal>
);
}

export default function RequestNewIntegration() {
return (
<RequestNewIntegrationWrapper gap="spaces-5">
<p>{createMessage(REQUEST_NEW_INTEGRATIONS.UNABLE_TO_FIND)}</p>
<RequestModal>
<Button
kind="secondary"
onClick={() => {
AnalyticsUtil.logEvent("REQUEST_INTEGRATION_CTA");
}}
>
{createMessage(REQUEST_NEW_INTEGRATIONS.REQUEST_NEW_BUTTON)}
</Button>
</RequestModal>
</RequestNewIntegrationWrapper>
);
}

0 comments on commit 11281c8

Please sign in to comment.