Răsfoiți Sursa

[POR-1973] intercom pop up on provisioning pre-flight check and v2 update errors (#3830)

Feroze Mohideen 2 ani în urmă
părinte
comite
6539bcc4d8

+ 18 - 12
dashboard/src/components/AzureProvisionerSettings.tsx

@@ -27,6 +27,7 @@ import Spacer from "./porter/Spacer";
 import Step from "./porter/Step";
 import Link from "./porter/Link";
 import Text from "./porter/Text";
+import { useIntercom } from "lib/hooks/useIntercom";
 
 const locationOptions = [
   { value: "eastus", label: "East US" },
@@ -89,9 +90,11 @@ const AzureProvisionerSettings: React.FC<Props> = (props) => {
   const [errorDetails, setErrorDetails] = useState<string>("");
   const [isClicked, setIsClicked] = useState(false);
 
-  const markStepStarted = async (step: string, region: string) => {
+  const { showIntercomWithMessage } = useIntercom();
+
+  const markStepStarted = async (step: string, {region, error_message}: {region?: string; error_message?: string}) => {
     try {
-      await api.updateOnboardingStep("<token>", { step, region, provider: "azure" }, {
+      await api.updateOnboardingStep("<token>", { step, region, error_message, provider: "azure" }, {
         project_id: currentProject.id,
       });
     } catch (err) {
@@ -224,7 +227,7 @@ const AzureProvisionerSettings: React.FC<Props> = (props) => {
       setErrorDetails("")
 
       if (!props.clusterId) {
-        markStepStarted("provisioning-started", azureLocation);
+        markStepStarted("provisioning-started", { region: azureLocation });
       }
 
       const res = await api.createContract("<token>", data, {
@@ -255,19 +258,22 @@ const AzureProvisionerSettings: React.FC<Props> = (props) => {
       setErrorMessage("");
       setErrorDetails("")
     } catch (err) {
-      const errMessage = err.response.data.error.replace("unknown: ", "");
+      showIntercomWithMessage({ message: "I am running into an issue provisioning a cluster." });
+      let errorMessage = DEFAULT_ERROR_MESSAGE;
+      let errorDetails = err.response?.data?.error?.replace("unknown: ", "") ?? "";
       // hacky, need to standardize error contract with backend
       setIsClicked(false);
-      if (errMessage.includes("resource provider")) {
-        setErrorMessage(AZURE_MISSING_RESOURCE_PROVIDER_MESSAGE);
-        setErrorDetails(errMessage)
-      } else if (errMessage.includes("quota")) {
-        setErrorMessage(AZURE_CORE_QUOTA_ERROR_MESSAGE)
-        setErrorDetails(errMessage)
+      if (errorDetails.includes("resource provider")) {
+        setErrorDetails(errorDetails);
+        errorMessage = AZURE_MISSING_RESOURCE_PROVIDER_MESSAGE;
+      } else if (errorDetails.includes("quota")) {
+        setErrorDetails(errorDetails);
+        errorMessage = AZURE_CORE_QUOTA_ERROR_MESSAGE;
       } else {
-        setErrorMessage(DEFAULT_ERROR_MESSAGE);
-        setErrorDetails("")
+        setErrorDetails("");
       }
+      setErrorMessage(errorMessage);
+      markStepStarted("provisioning-failed", { error_message: `Error message: ${errorMessage}; Error details: ${errorDetails}` });
     } finally {
       setIsReadOnly(false);
       setIsClicked(false);

+ 5 - 1
dashboard/src/components/GCPProvisionerSettings.tsx

@@ -38,6 +38,7 @@ import Fieldset from "./porter/Fieldset";
 import ExpandableSection from "./porter/ExpandableSection";
 import PreflightChecks from "./PreflightChecks";
 import VerticalSteps from "./porter/VerticalSteps";
+import { useIntercom } from "lib/hooks/useIntercom";
 
 
 const locationOptions = [
@@ -92,6 +93,8 @@ const GCPProvisionerSettings: React.FC<Props> = (props) => {
   const [isExpanded, setIsExpanded] = useState(false);
   const [preflightError, setPreflightError] = useState<string>("")
 
+  const { showIntercomWithMessage } = useIntercom();
+
   const markStepStarted = async (step: string, region?: string) => {
     try {
       await api.updateOnboardingStep("<token>", { step, provider: "gcp", region }, {
@@ -429,10 +432,11 @@ const GCPProvisionerSettings: React.FC<Props> = (props) => {
           errors = errors + check + ", "
         }
       }
-      // If none of the checks have a message, set setPreflightFailed to false
       if (hasMessage) {
+        showIntercomWithMessage({ message: "I am running into an issue provisioning a cluster." });
         markStepStarted("provisioning-failed", errors);
       }
+      // If none of the checks have a message, set setPreflightFailed to false
       if (!hasMessage) {
         setPreflightFailed(false);
         setStep(2);

+ 4 - 0
dashboard/src/components/ProvisionerSettings.tsx

@@ -42,6 +42,7 @@ import Loading from "./Loading";
 import PreflightChecks from "./PreflightChecks";
 import Placeholder from "./Placeholder";
 import VerticalSteps from "./porter/VerticalSteps";
+import { useIntercom } from "lib/hooks/useIntercom";
 const regionOptions = [
   { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
   { value: "us-east-2", label: "US East (Ohio) us-east-2" },
@@ -146,6 +147,8 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   const [preflightData, setPreflightData] = useState(null)
   const [preflightFailed, setPreflightFailed] = useState<boolean>(true)
   const [preflightError, setPreflightError] = useState<string>("")
+  
+  const { showIntercomWithMessage } = useIntercom();
 
   const markStepStarted = async (step: string, errMessage?: string) => {
     try {
@@ -537,6 +540,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
       }
       // If none of the checks have a message, set setPreflightFailed to false
       if (hasMessage) {
+        showIntercomWithMessage({ message: "I am running into an issue provisioning a cluster." });
         markStepStarted("provisioning-failed", errors);
       }
       if (!hasMessage) {

+ 1 - 0
dashboard/src/hosted.index.html

@@ -7,6 +7,7 @@
       window.intercomSettings = {
         app_id: "<%= htmlWebpackPlugin.options.intercomAppId %>",
         custom_launcher_selector: "#intercom_help",
+        alignment: 'right',
       };
     </script>
 

+ 2 - 1
dashboard/src/index.html

@@ -46,7 +46,8 @@
   <script>
     window.intercomSettings = {
       api_base: "https://api-iam.intercom.io",
-      app_id: "gq56g49i"
+      app_id: "gq56g49i",
+      alignment: 'right',
     };
   </script>
   

+ 1 - 0
dashboard/src/index.tsx

@@ -10,6 +10,7 @@ import { EnableErrorHandling } from "shared/error_handling/window_error_handling
 declare global {
   interface Window {
     analytics: any;
+    Intercom: any;
   }
 }
 

+ 19 - 0
dashboard/src/lib/hooks/useIntercom.ts

@@ -0,0 +1,19 @@
+// useIntercom contains all the utility methods related to the Intercom chat widget
+export const useIntercom = () => {
+    const showIntercomWithMessageAfterDelay = (message: string, delaySeconds: number) => {
+        const func = () => {
+            if (typeof window.Intercom === 'function') {
+                window.Intercom('showNewMessage', message);
+            }
+        }
+        setTimeout(func, delaySeconds * 1000);
+    }
+
+    const showIntercomWithMessage = ({ message, delaySeconds = 3 }: { message: string, delaySeconds?: number }) => {
+        showIntercomWithMessageAfterDelay(message, delaySeconds);
+    }
+
+    return {
+        showIntercomWithMessage,
+    }
+}

+ 5 - 1
dashboard/src/main/home/app-dashboard/app-view/AppDataContainer.tsx

@@ -47,6 +47,7 @@ import axios from "axios";
 import HelmEditorTab from "./tabs/HelmEditorTab";
 import HelmLatestValuesTab from "./tabs/HelmLatestValuesTab";
 import { Context } from "shared/Context";
+import { useIntercom } from "lib/hooks/useIntercom";
 
 // commented out tabs are not yet implemented
 // will be included as support is available based on data from app revisions rather than helm releases
@@ -83,6 +84,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
   const { currentProject, user } = useContext(Context);
 
   const { updateAppStep } = useAppAnalytics();
+  const { showIntercomWithMessage } = useIntercom();
 
   const {
     porterApp: porterAppRecord,
@@ -305,6 +307,8 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
       // redirect to the default tab after save
       history.push(`/apps/${porterAppRecord.name}/${DEFAULT_TAB}`);
     } catch (err) {
+      showIntercomWithMessage({ message: "I am running into an issue updating my application." });
+      
       let message =
         "App update failed: please try again or contact support@porter.run if the error persists.";
       let stack = "Unable to get error stack";
@@ -382,7 +386,6 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
     const errorKeys = Object.keys(errors);
     if (errorKeys.length > 0) {
       const stringifiedJson = JSON.stringify(errors);
-
       let errorMessage =
         "App update failed. Please try again. If the error persists, please contact support@porter.run.";
       if (errorKeys.includes("app")) {
@@ -411,6 +414,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
         }
       }
 
+      showIntercomWithMessage({ message: "I am running into an issue updating my application." });
       updateAppStep({
         step: "porter-app-update-failure",
         errorMessage: `Form validation error (visible to user): ${errorMessage}. Stringified JSON errors (invisible to user): ${stringifiedJson}`,

+ 7 - 0
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -50,6 +50,7 @@ import EnvSettings from "../validate-apply/app-settings/EnvSettings";
 import ImageSettings from "../image-settings/ImageSettings";
 import { useClusterResources } from "shared/ClusterResourcesContext";
 import PorterYamlModal from "./PorterYamlModal";
+import { useIntercom } from "lib/hooks/useIntercom";
 
 type CreateAppProps = {} & RouteComponentProps;
 
@@ -70,6 +71,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
   };
   const [isNameHighlight, setIsNameHighlight] = React.useState(false);
 
+  const { showIntercomWithMessage } = useIntercom();
+
   const [
     validatedAppProto,
     setValidatedAppProto,
@@ -336,6 +339,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
 
         return true;
       } catch (err) {
+        showIntercomWithMessage({ message: "I am running into an issue launching an application." });
+
         if (axios.isAxiosError(err) && err.response?.data?.error) {
           updateAppStep({
             step: "stack-launch-failure",
@@ -430,6 +435,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
         }
       }
 
+      showIntercomWithMessage({ message: "I am running into an issue launching an application." });
+
       updateAppStep({
         step: "stack-launch-failure",
         errorMessage: `Form validation error: ${errorMessage}`,

+ 7 - 4
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Networking.tsx

@@ -40,15 +40,18 @@ const Networking: React.FC<NetworkingProps> = ({ index, service, internalNetwork
   }, [service.name.value, namespace, port]);
 
   const getApplicationURLText = () => {
-    if (service.config.domains.length !== 0) {
+    const numNonEmptyDomains = service.config.domains.filter(
+      (d) => d.name.value !== ""
+    );
+    if (numNonEmptyDomains.length !== 0) {
       return (
         <Text>
-          {`External URL${service.config.domains.length === 1 ? "" : "s"}: `}
-          {service.config.domains.map((d, i) => {
+          {`External URL${numNonEmptyDomains.length === 1 ? "" : "s"}: `}
+          {numNonEmptyDomains.map((d, i) => {
             return (
               <a href={prefixSubdomain(d.name.value)} target="_blank">
                 {d.name.value}
-                {i !== service.config.domains.length - 1 && ", "}
+                {i !== numNonEmptyDomains.length - 1 && ", "}
               </a>
             );
           })}