diff --git a/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js
index 776ae252a0..dd83568807 100644
--- a/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js
+++ b/app/client/cypress/e2e/Regression/ClientSide/Git/GitSync/RepoLimitExceededErrorModal_spec.js
@@ -13,7 +13,8 @@ describe("Repo Limit Exceeded Error Modal", function () {
repoName2 = uuid.v4().split("-")[0];
repoName3 = uuid.v4().split("-")[0];
repoName4 = uuid.v4().split("-")[0];
- _.agHelper.ClickButton("Build on my own");
+ _.agHelper.AssertElementVisible(_.locators._sidebar);
+ _.onboarding.closeIntroModal();
});
it("1. Modal should be opened with proper components", function () {
diff --git a/app/client/cypress/e2e/Regression/ClientSide/Onboarding/FirstTimeUserOnboarding_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Onboarding/FirstTimeUserOnboarding_spec.js
index 90be4752d2..7f5ce85f03 100644
--- a/app/client/cypress/e2e/Regression/ClientSide/Onboarding/FirstTimeUserOnboarding_spec.js
+++ b/app/client/cypress/e2e/Regression/ClientSide/Onboarding/FirstTimeUserOnboarding_spec.js
@@ -6,6 +6,7 @@ import {
homePage,
onboarding,
draggableWidgets,
+ debuggerHelper,
} from "../../../../support/Objects/ObjectsCore";
const datasource = require("../../../../locators/DatasourcesEditor.json");
@@ -19,7 +20,7 @@ describe("FirstTimeUserOnboarding", function () {
it("1. onboarding flow - should check page entity selection in explorer", function () {
cy.get(OnboardingLocator.introModal).should("be.visible");
- cy.get(OnboardingLocator.introModalBuild).click();
+ cy.get(OnboardingLocator.checklistDatasourceBtn).click();
cy.get(OnboardingLocator.introModal).should("not.exist");
cy.get(".t--entity-name:contains(Page1)")
.trigger("mouseover")
@@ -29,42 +30,43 @@ describe("FirstTimeUserOnboarding", function () {
it(
"excludeForAirgap",
- "2. onboarding flow - should check the checklist page actions",
+ "2. onboarding flow - should check the checklist actions",
function () {
- agHelper.GetNClick(OnboardingLocator.introModalBuild);
- agHelper.GetNClick(OnboardingLocator.statusbar);
agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "0 of 5");
- agHelper.GetNClick(OnboardingLocator.checklistBack);
- agHelper.GetNClick(OnboardingLocator.statusbar);
- agHelper.AssertElementEnabledDisabled(
- OnboardingLocator.checklistDatasourceBtn,
- 0,
- false,
- );
+ agHelper.AssertElementExist(OnboardingLocator.checklistDatasourceBtn);
agHelper.GetNClick(OnboardingLocator.checklistDatasourceBtn);
agHelper.AssertElementVisible(OnboardingLocator.datasourcePage);
agHelper.GetNClick(OnboardingLocator.datasourceMock);
agHelper.Sleep();
- agHelper.GetNClick(OnboardingLocator.statusbar);
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "1 of 5");
- agHelper.AssertElementAbsence(OnboardingLocator.checklistDatasourceBtn);
+ agHelper
+ .GetElement(OnboardingLocator.checklistDatasourceBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
agHelper.GetNClick(OnboardingLocator.checklistActionBtn);
agHelper.GetNClick(OnboardingLocator.createQuery);
agHelper.Sleep();
- agHelper.GetNClick(OnboardingLocator.statusbar);
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "2 of 5");
- agHelper.AssertElementAbsence(OnboardingLocator.checklistActionBtn);
+ agHelper
+ .GetElement(OnboardingLocator.checklistActionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
agHelper.GetNClick(OnboardingLocator.checklistWidgetBtn);
agHelper.AssertElementVisible(OnboardingLocator.widgetSidebar);
entityExplorer.DragDropWidgetNVerify(draggableWidgets.TEXT);
- agHelper.GetNClick(OnboardingLocator.statusbar);
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "3 of 5");
- agHelper.AssertElementAbsence(OnboardingLocator.checklistWidgetBtn);
+ agHelper
+ .GetElement(OnboardingLocator.checklistWidgetBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
agHelper.GetNClick(OnboardingLocator.checklistConnectionBtn);
agHelper.AssertElementVisible(OnboardingLocator.snipingBanner);
@@ -75,9 +77,12 @@ describe("FirstTimeUserOnboarding", function () {
.wait(500);
agHelper.GetNClick(OnboardingLocator.widgetName);
- agHelper.GetNClick(OnboardingLocator.statusbar);
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "4 of 5");
- agHelper.AssertElementAbsence(OnboardingLocator.checklistConnectionBtn);
+ agHelper
+ .GetElement(OnboardingLocator.checklistConnectionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
let open;
cy.window().then((window) => {
@@ -86,7 +91,8 @@ describe("FirstTimeUserOnboarding", function () {
});
agHelper.GetNClick(OnboardingLocator.checklistDeployBtn);
- agHelper.GetNAssertContains(OnboardingLocator.checklistStatus, "5 of 5");
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
+ agHelper.AssertElementExist(OnboardingLocator.checklistCompletionBanner);
agHelper.AssertElementAbsence(OnboardingLocator.checklistDeployBtn);
cy.window().then((window) => {
@@ -99,17 +105,16 @@ describe("FirstTimeUserOnboarding", function () {
"airgap",
"2. onboarding flow - should check the checklist page actions - airgap",
function () {
- cy.get(OnboardingLocator.introModalBuild).click();
+ cy.get(OnboardingLocator.introModal).should("be.visible");
- cy.get(OnboardingLocator.statusbar).click();
cy.get(OnboardingLocator.checklistStatus).should("be.visible");
cy.get(OnboardingLocator.checklistStatus).should("contain", "0 of 5");
- cy.get(OnboardingLocator.checklistBack).click();
- cy.get(OnboardingLocator.statusbar).click();
- cy.get(OnboardingLocator.checklistDatasourceBtn).should(
- "not.be.disabled",
- );
+ agHelper
+ .GetElement(OnboardingLocator.checklistDatasourceBtn)
+ .realHover()
+ .should("have.css", "cursor", "pointer");
+
cy.get(OnboardingLocator.checklistDatasourceBtn).click();
cy.get(OnboardingLocator.datasourcePage).should("be.visible");
cy.get(datasource.MongoDB).click();
@@ -120,24 +125,33 @@ describe("FirstTimeUserOnboarding", function () {
});
cy.testSaveDatasource();
cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "1 of 5");
- cy.get(OnboardingLocator.checklistDatasourceBtn).should("not.exist");
+ agHelper
+ .GetElement(OnboardingLocator.checklistDatasourceBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
cy.get(OnboardingLocator.checklistActionBtn).should("be.visible");
cy.get(OnboardingLocator.checklistActionBtn).click();
cy.get(OnboardingLocator.createQuery).should("be.visible");
cy.get(OnboardingLocator.createQuery).click();
cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "2 of 5");
- cy.get(OnboardingLocator.checklistActionBtn).should("not.exist");
+ agHelper
+ .GetElement(OnboardingLocator.checklistActionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
cy.get(OnboardingLocator.checklistWidgetBtn).should("be.visible");
cy.get(OnboardingLocator.checklistWidgetBtn).click();
cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.statusbar).click();
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "3 of 5");
- cy.get(OnboardingLocator.checklistWidgetBtn).should("not.exist");
+ agHelper
+ .GetElement(OnboardingLocator.checklistWidgetBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
cy.get(OnboardingLocator.checklistConnectionBtn).should("be.visible");
cy.get(OnboardingLocator.checklistConnectionBtn).click();
@@ -148,9 +162,12 @@ describe("FirstTimeUserOnboarding", function () {
.wait(500);
cy.get(OnboardingLocator.widgetName).should("be.visible");
cy.get(OnboardingLocator.widgetName).click();
- cy.get(OnboardingLocator.statusbar).click();
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "4 of 5");
- cy.get(OnboardingLocator.checklistConnectionBtn).should("not.exist");
+ agHelper
+ .GetElement(OnboardingLocator.checklistConnectionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
let open;
cy.window().then((window) => {
@@ -159,172 +176,17 @@ describe("FirstTimeUserOnboarding", function () {
});
cy.get(OnboardingLocator.checklistDeployBtn).should("be.visible");
cy.get(OnboardingLocator.checklistDeployBtn).click();
- cy.get(OnboardingLocator.checklistStatus).should("contain", "5 of 5");
- cy.get(OnboardingLocator.checklistDeployBtn).should("not.exist");
+ agHelper.AssertElementExist(OnboardingLocator.checklistCompletionBanner);
+ agHelper.AssertElementAbsence(OnboardingLocator.checklistDeployBtn);
cy.window().then((window) => {
window.open = open;
});
},
);
- it(
- "excludeForAirgap",
- "3. onboarding flow - should check the tasks page actions",
- function () {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_DATASOURCE_HEADER(),
- );
- cy.get(OnboardingLocator.taskDatasourceBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(OnboardingLocator.datasourceMock).first().click();
- cy.wait(1000);
- cy.get(OnboardingLocator.datasourceBackBtn).click();
- cy.get(OnboardingLocator.taskDatasourceBtn).should("not.exist");
-
- cy.get(OnboardingLocator.taskActionBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_QUERY_HEADER(),
- );
- cy.get(OnboardingLocator.taskActionBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(OnboardingLocator.createQuery).first().click();
- cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
- cy.get(OnboardingLocator.checklistBack).click();
- cy.get(OnboardingLocator.taskActionBtn).should("not.exist");
-
- cy.get(OnboardingLocator.taskWidgetBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_WIDGET_HEADER(),
- );
- cy.get(OnboardingLocator.taskWidgetBtn).click();
- cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
- cy.get(OnboardingLocator.dropTarget).should("be.visible");
- cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.textWidgetName).should("be.visible");
- cy.get(OnboardingLocator.taskWidgetBtn).should("not.exist");
- },
- );
-
- it(
- "airgap",
- "3. onboarding flow - should check the tasks page actions - airgap",
- function () {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_DATASOURCE_HEADER(),
- );
- cy.get(OnboardingLocator.taskDatasourceBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(datasource.MongoDB).click();
- cy.fillMongoDatasourceForm();
- cy.generateUUID().then((uid) => {
- datasourceName = `Mongo CRUD ds ${uid}`;
- cy.renameDatasource(datasourceName);
- });
- cy.testSaveDatasource();
- cy.wait(1000);
- cy.get(".t--close-editor").click();
- cy.wait(1000);
- cy.get(OnboardingLocator.datasourceBackBtn).click();
- cy.get(OnboardingLocator.taskDatasourceBtn).should("not.exist");
-
- cy.get(OnboardingLocator.taskActionBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_QUERY_HEADER(),
- );
- cy.get(OnboardingLocator.taskActionBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(OnboardingLocator.createQuery).first().click();
- cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
- cy.get(OnboardingLocator.checklistBack).click();
- cy.get(OnboardingLocator.taskActionBtn).should("not.exist");
-
- cy.get(OnboardingLocator.taskWidgetBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceHeader).contains(
- Cypress.env("MESSAGES").ONBOARDING_TASK_WIDGET_HEADER(),
- );
- cy.get(OnboardingLocator.taskWidgetBtn).click();
- cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
- cy.get(OnboardingLocator.dropTarget).should("be.visible");
- cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.textWidgetName).should("be.visible");
- cy.get(OnboardingLocator.taskWidgetBtn).should("not.exist");
- },
- );
-
- it("4. onboarding flow - should check the tasks page datasource action alternate widget action", function () {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceAltBtn).click();
- cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
- cy.get(OnboardingLocator.dropTarget).should("be.visible");
- cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.textWidgetName).should("be.visible");
- });
-
- it(
- "airgap",
- "5. onboarding flow - should check the tasks page query action alternate widget action - airgap",
- function () {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(datasource.MongoDB).click();
- cy.fillMongoDatasourceForm();
- cy.generateUUID().then((uid) => {
- datasourceName = `Mongo CRUD ds ${uid}`;
- cy.renameDatasource(datasourceName);
- });
- cy.testSaveDatasource();
- cy.wait(1000);
- cy.get(".t--close-editor").click();
- cy.wait(1000);
- cy.get(OnboardingLocator.datasourceBackBtn).click();
-
- cy.get(OnboardingLocator.taskActionBtn).should("be.visible");
- cy.get(OnboardingLocator.taskActionAltBtn).click();
- cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
- cy.get(OnboardingLocator.dropTarget).should("be.visible");
- cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.textWidgetName).should("be.visible");
- },
- );
-
- it(
- "excludeForAirgap",
- "5. onboarding flow - should check the tasks page query action alternate widget action",
- function () {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
- cy.get(OnboardingLocator.taskDatasourceBtn).click();
- cy.get(OnboardingLocator.datasourcePage).should("be.visible");
- cy.get(OnboardingLocator.datasourceMock).first().click();
- cy.wait(1000);
- cy.get(OnboardingLocator.datasourceBackBtn).click();
-
- cy.get(OnboardingLocator.taskActionBtn).should("be.visible");
- cy.get(OnboardingLocator.taskActionAltBtn).click();
- cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
- cy.get(OnboardingLocator.dropTarget).should("be.visible");
- cy.dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.textWidgetName).should("be.visible");
- },
- );
-
- it("6. onboarding flow - should check directly opening widget pane", function () {
- cy.get(OnboardingLocator.introModalBuild).click();
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
+ it("3. onboarding flow - should check directly opening widget pane", function () {
+ cy.get(OnboardingLocator.checklistDatasourceBtn).should("be.visible");
+ agHelper.GetNClick(OnboardingLocator.introModalCloseBtn);
entityExplorer.NavigateToSwitcher("Widgets");
cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
cy.get(OnboardingLocator.dropTarget).should("be.visible");
@@ -336,21 +198,23 @@ describe("FirstTimeUserOnboarding", function () {
"response.body.responseMeta.status",
200,
);
- cy.get(OnboardingLocator.statusbar).should("be.visible");
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
+ agHelper.AssertElementVisible(OnboardingLocator.introModal);
cy.get(OnboardingLocator.textWidgetName).should("be.visible");
});
- it("7. onboarding flow - new apps created should start with signposting", function () {
- cy.get(OnboardingLocator.introModalBuild).click();
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
+ it("4. onboarding flow - new apps created should start with signposting", function () {
+ cy.get(OnboardingLocator.checklistDatasourceBtn).should("be.visible");
+ agHelper.GetNClick(OnboardingLocator.introModalCloseBtn);
homePage.NavigateToHome();
homePage.CreateNewApplication(false);
- cy.get(OnboardingLocator.taskDatasourceBtn).should("be.visible");
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
+ cy.get(OnboardingLocator.checklistDatasourceBtn).should("be.visible");
});
- it("8. onboarding flow - once signposting is completed new apps won't start with signposting", function () {
+ it("5. onboarding flow - once signposting is completed new apps won't start with signposting", function () {
onboarding.completeSignposting();
homePage.NavigateToHome();
@@ -358,6 +222,7 @@ describe("FirstTimeUserOnboarding", function () {
homePage.CreateNewApplication(false);
agHelper.AssertElementExist(locators._dropHere);
- agHelper.AssertElementAbsence(OnboardingLocator.statusbar);
+ agHelper.GetNClick(debuggerHelper.locators._helpButton);
+ agHelper.AssertElementAbsence(OnboardingLocator.introModal);
});
});
diff --git a/app/client/cypress/e2e/Regression/ClientSide/Onboarding/GuidedTour_spec.js b/app/client/cypress/e2e/Regression/ClientSide/Onboarding/GuidedTour_spec.js
index 4a656eda7a..f09ccb5429 100644
--- a/app/client/cypress/e2e/Regression/ClientSide/Onboarding/GuidedTour_spec.js
+++ b/app/client/cypress/e2e/Regression/ClientSide/Onboarding/GuidedTour_spec.js
@@ -16,8 +16,8 @@ describe("excludeForAirgap", "Guided Tour", function () {
cy.generateUUID().then((uid) => {
cy.Signup(`${uid}@appsmith.com`, uid);
});
- cy.get(onboardingLocators.introModalWelcomeTourBtn).should("be.visible");
- cy.get(onboardingLocators.introModalWelcomeTourBtn).click();
+ cy.get(onboardingLocators.editorWelcomeTourBtn).should("be.visible");
+ cy.get(onboardingLocators.editorWelcomeTourBtn).click();
cy.get(onboardingLocators.welcomeTourBtn).should("be.visible");
});
diff --git a/app/client/cypress/locators/FirstTimeUserOnboarding.json b/app/client/cypress/locators/FirstTimeUserOnboarding.json
index 5127352792..707cc1c4e7 100644
--- a/app/client/cypress/locators/FirstTimeUserOnboarding.json
+++ b/app/client/cypress/locators/FirstTimeUserOnboarding.json
@@ -1,17 +1,12 @@
{
- "introModal": ".ads-v2-modal__content",
- "introModalBuild": ".t--introduction-modal-build-button",
- "introModalWelcomeTourBtn": ".t--introduction-modal-welcome-tour-button",
- "introModalCloseBtn": ".ads-v2-modal__content-header-close-button",
- "statusbar": ".t--onboarding-statusbar",
- "statusbarClose": "[data-testid='statusbar-skip']",
- "checklistStatus": ".t--checklist-complete-status",
- "checklistDatasourceBtn": ".t--checklist-datasource-button",
- "checklistBack": ".t--checklist-back",
- "checklistActionBtn": ".t--checklist-action-button",
- "checklistWidgetBtn": ".t--checklist-widget-button",
- "checklistConnectionBtn": ".t--checklist-connection-button",
- "checklistDeployBtn": ".t--checklist-deploy-button",
+ "introModal": "[data-testid='signposting-modal']",
+ "introModalCloseBtn": "[data-testid='signposting-modal-close-btn']",
+ "checklistStatus": "[data-testid='checklist-completion-info']",
+ "checklistDatasourceBtn": "[data-testid='checklist-datasource']",
+ "checklistActionBtn": "[data-testid='checklist-action']",
+ "checklistWidgetBtn": "[data-testid='checklist-widget']",
+ "checklistConnectionBtn": "[data-testid='checklist-connection']",
+ "checklistDeployBtn": "[data-testid='checklist-deploy']",
"datasourcePage": ".t--integrationsHomePage",
"datasourceMock": ".t--mock-datasource",
"createQuery": ".t--create-query",
@@ -20,13 +15,9 @@
"snipingTextWidget": ".t--snipeable-textwidget",
"widgetName": ".t--widget-name",
"datasourceBackBtn": ".t--back-button",
- "taskDatasourceBtn": ".t--tasks-datasource-button",
- "taskDatasourceHeader": ".t--tasks-datasource-header",
- "taskActionBtn": ".t--tasks-action-button",
- "taskWidgetBtn": ".t--tasks-widget-button",
"dropTarget": ".t--drop-target",
"textWidgetName": ".t--widget-textwidget",
- "taskDatasourceAltBtn": ".t--tasks-datasource-alternate-button",
- "taskActionAltBtn": ".t--tasks-action-alternate-button",
- "welcomeTourBtn": ".t--start-building"
+ "welcomeTourBtn": ".t--start-building",
+ "editorWelcomeTourBtn": "[data-testid='editor-welcome-tour']",
+ "checklistCompletionBanner": "[data-testid='checklist-completion-banner']"
}
diff --git a/app/client/cypress/support/Pages/HomePage.ts b/app/client/cypress/support/Pages/HomePage.ts
index 6db40c9df2..7ab97cea99 100644
--- a/app/client/cypress/support/Pages/HomePage.ts
+++ b/app/client/cypress/support/Pages/HomePage.ts
@@ -234,7 +234,6 @@ export class HomePage {
if (skipSignposting) {
this.agHelper.AssertElementVisible(this.entityExplorer._entityExplorer);
this.onboarding.closeIntroModal();
- this.onboarding.skipSignposting();
}
this.assertHelper.AssertNetworkStatus("getWorkspace");
}
diff --git a/app/client/cypress/support/Pages/Onboarding.ts b/app/client/cypress/support/Pages/Onboarding.ts
index 7981373fb1..5b865e517f 100644
--- a/app/client/cypress/support/Pages/Onboarding.ts
+++ b/app/client/cypress/support/Pages/Onboarding.ts
@@ -6,19 +6,19 @@ let datasourceName;
export class Onboarding {
private _aggregateHelper = ObjectsRegistry.AggregateHelper;
private _datasources = ObjectsRegistry.DataSources;
+ private _debuggerHelper = ObjectsRegistry.DebuggerHelper;
completeSignposting() {
- cy.get(OnboardingLocator.introModalBuild).click();
-
- cy.get(OnboardingLocator.statusbar).click();
cy.get(OnboardingLocator.checklistStatus).should("be.visible");
cy.get(OnboardingLocator.checklistStatus).should("contain", "0 of 5");
- cy.get(OnboardingLocator.checklistBack).click();
- cy.get(OnboardingLocator.statusbar).click();
- cy.get(OnboardingLocator.checklistDatasourceBtn).should("not.be.disabled");
+ this._aggregateHelper
+ .GetElement(OnboardingLocator.checklistConnectionBtn)
+ .realHover()
+ .should("have.css", "cursor", "not-allowed");
cy.get(OnboardingLocator.checklistDatasourceBtn).click();
cy.get(OnboardingLocator.datasourcePage).should("be.visible");
+ this._aggregateHelper.AssertElementAbsence(OnboardingLocator.introModal);
if (Cypress.env("AIRGAPPED")) {
this._datasources.CreateDataSource("Mongo");
cy.get("@dsName").then(($dsName) => {
@@ -28,24 +28,33 @@ export class Onboarding {
cy.get(OnboardingLocator.datasourceMock).first().click();
}
cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
+ this._aggregateHelper.GetNClick(this._debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "1 of 5");
- cy.get(OnboardingLocator.checklistDatasourceBtn).should("not.exist");
+ this._aggregateHelper
+ .GetElement(OnboardingLocator.checklistConnectionBtn)
+ .realHover()
+ .should("have.css", "cursor", "not-allowed");
cy.get(OnboardingLocator.checklistActionBtn).should("be.visible");
cy.get(OnboardingLocator.checklistActionBtn).click();
cy.get(OnboardingLocator.createQuery).should("be.visible");
cy.get(OnboardingLocator.createQuery).click();
cy.wait(1000);
- cy.get(OnboardingLocator.statusbar).click();
+ this._aggregateHelper.GetNClick(this._debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "2 of 5");
- cy.get(OnboardingLocator.checklistActionBtn).should("not.exist");
+ this._aggregateHelper
+ .GetElement(OnboardingLocator.checklistActionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
cy.get(OnboardingLocator.checklistWidgetBtn).should("be.visible");
cy.get(OnboardingLocator.checklistWidgetBtn).click();
cy.get(OnboardingLocator.widgetSidebar).should("be.visible");
(cy as any).dragAndDropToCanvas("textwidget", { x: 400, y: 400 });
- cy.get(OnboardingLocator.statusbar).click();
+ this._aggregateHelper.GetNClick(this._debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "3 of 5");
- cy.get(OnboardingLocator.checklistWidgetBtn).should("not.exist");
+ this._aggregateHelper
+ .GetElement(OnboardingLocator.checklistWidgetBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
cy.get(OnboardingLocator.checklistConnectionBtn).should("be.visible");
cy.get(OnboardingLocator.checklistConnectionBtn).click();
@@ -56,9 +65,12 @@ export class Onboarding {
.wait(500);
cy.get(OnboardingLocator.widgetName).should("be.visible");
cy.get(OnboardingLocator.widgetName).click();
- cy.get(OnboardingLocator.statusbar).click();
+ this._aggregateHelper.GetNClick(this._debuggerHelper.locators._helpButton);
cy.get(OnboardingLocator.checklistStatus).should("contain", "4 of 5");
- cy.get(OnboardingLocator.checklistConnectionBtn).should("not.exist");
+ this._aggregateHelper
+ .GetElement(OnboardingLocator.checklistConnectionBtn)
+ .realHover()
+ .should("have.css", "cursor", "auto");
let open: any;
cy.window().then((window: any) => {
@@ -67,8 +79,13 @@ export class Onboarding {
});
cy.get(OnboardingLocator.checklistDeployBtn).should("be.visible");
cy.get(OnboardingLocator.checklistDeployBtn).click();
- cy.get(OnboardingLocator.checklistStatus).should("contain", "5 of 5");
- cy.get(OnboardingLocator.checklistDeployBtn).should("not.exist");
+ this._aggregateHelper.AssertElementAbsence(OnboardingLocator.introModal);
+ this._aggregateHelper.Sleep();
+
+ this._aggregateHelper.GetNClick(this._debuggerHelper.locators._helpButton);
+ this._aggregateHelper.AssertElementExist(
+ OnboardingLocator.checklistCompletionBanner,
+ );
cy.window().then((window) => {
window.open = open;
});
@@ -81,12 +98,4 @@ export class Onboarding {
}
});
}
-
- skipSignposting() {
- cy.get("body").then(($body) => {
- if ($body.find(OnboardingLocator.statusbarClose).length) {
- this._aggregateHelper.GetNClick(OnboardingLocator.statusbarClose);
- }
- });
- }
}
diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js
index 3163aa2ee0..71484c9dc9 100644
--- a/app/client/cypress/support/commands.js
+++ b/app/client/cypress/support/commands.js
@@ -2151,5 +2151,4 @@ Cypress.Commands.add("SelectFromMultiSelect", (options) => {
Cypress.Commands.add("skipSignposting", () => {
onboarding.closeIntroModal();
- onboarding.skipSignposting();
});
diff --git a/app/client/src/actions/onboardingActions.ts b/app/client/src/actions/onboardingActions.ts
index aa6ff12593..ba7a2efbad 100644
--- a/app/client/src/actions/onboardingActions.ts
+++ b/app/client/src/actions/onboardingActions.ts
@@ -1,4 +1,5 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
+import type { SIGNPOSTING_STEP } from "pages/Editor/FirstTimeUserOnboarding/Utils";
import type { GUIDED_TOUR_STEPS } from "pages/Editor/GuidedTour/constants";
import type { GuidedTourState } from "reducers/uiReducers/guidedTourReducer";
import type { WidgetProps } from "widgets/BaseWidget";
@@ -26,6 +27,13 @@ export const removeFirstTimeUserOnboardingApplicationId = (
};
};
+export const showSignpostingModal = (payload: boolean) => {
+ return {
+ type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
+ payload,
+ };
+};
+
export const disableStartSignpostingAction = () => {
return {
type: ReduxActionTypes.DISABLE_START_SIGNPOSTING,
@@ -45,6 +53,54 @@ export const firstTimeUserOnboardingInit = (
};
};
+export const setSignpostingOverlay = (payload: boolean) => {
+ return {
+ type: ReduxActionTypes.SET_SIGNPOSTING_OVERLAY,
+ payload,
+ };
+};
+
+export const signpostingMarkAllRead = () => {
+ return {
+ type: ReduxActionTypes.SIGNPOSTING_MARK_ALL_READ,
+ };
+};
+
+export const signpostingStepUpdateInit = (payload: {
+ step: SIGNPOSTING_STEP;
+ completed: boolean;
+}) => {
+ return {
+ type: ReduxActionTypes.SIGNPOSTING_STEP_UPDATE_INIT,
+ payload,
+ };
+};
+
+export const signpostingStepUpdate = (payload: {
+ step: SIGNPOSTING_STEP;
+ completed: boolean;
+ read?: boolean;
+}) => {
+ return {
+ type: ReduxActionTypes.SIGNPOSTING_STEP_UPDATE,
+ payload,
+ };
+};
+
+export const showSignpostingTooltip = (payload: boolean) => {
+ return {
+ type: ReduxActionTypes.SIGNPOSTING_SHOW_TOOLTIP,
+ payload,
+ };
+};
+
+export const showAnonymousDataPopup = (payload: boolean) => {
+ return {
+ type: ReduxActionTypes.SHOW_ANONYMOUS_DATA_POPUP,
+ payload,
+ };
+};
+
export const markStepComplete = () => {
return {
type: ReduxActionTypes.GUIDED_TOUR_MARK_STEP_COMPLETED,
diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx
index b422002fbc..99e2aa837e 100644
--- a/app/client/src/ce/constants/ReduxActionConstants.tsx
+++ b/app/client/src/ce/constants/ReduxActionConstants.tsx
@@ -616,6 +616,12 @@ const ActionTypes = {
SET_FORCE_WIDGET_PANEL_OPEN: "SET_FORCE_WIDGET_PANEL_OPEN",
END_FIRST_TIME_USER_ONBOARDING: "END_FIRST_TIME_USER_ONBOARDING",
UNDO_END_FIRST_TIME_USER_ONBOARDING: "UNDO_END_FIRST_TIME_USER_ONBOARDING",
+ SET_SIGNPOSTING_OVERLAY: "SET_SIGNPOSTING_OVERLAY",
+ SIGNPOSTING_MARK_ALL_READ: "SIGNPOSTING_MARK_ALL_READ",
+ SIGNPOSTING_STEP_UPDATE_INIT: "SIGNPOSTING_STEP_UPDATE_INIT",
+ SIGNPOSTING_STEP_UPDATE: "SIGNPOSTING_STEP_UPDATE",
+ SIGNPOSTING_SHOW_TOOLTIP: "SIGNPOSTING_SHOW_TOOLTIP",
+ SHOW_ANONYMOUS_DATA_POPUP: "SHOW_ANONYMOUS_DATA_POPUP",
FETCH_ADMIN_SETTINGS: "FETCH_ADMIN_SETTINGS",
FETCH_ADMIN_SETTINGS_SUCCESS: "FETCH_ADMIN_SETTINGS_SUCCESS",
FETCH_ADMIN_SETTINGS_ERROR: "FETCH_ADMIN_SETTINGS_ERROR",
diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts
index a935eb6ad3..1d205923ae 100644
--- a/app/client/src/ce/constants/messages.ts
+++ b/app/client/src/ce/constants/messages.ts
@@ -982,24 +982,33 @@ export const ONBOARDING_CHECKLIST_BODY = () =>
"Let’s get you started on your first application, explore Appsmith yourself or follow our guide below to discover what Appsmith can do.";
export const ONBOARDING_CHECKLIST_COMPLETE_TEXT = () => "complete";
+export const SIGNPOSTING_POPUP_SUBTITLE = () =>
+ "These are all the things you need to do to build your first application.";
+export const SIGNPOSTING_SUCCESS_POPUP = {
+ title: () => "🎉 Awesome! You’ve explored the basics of Appsmith",
+ subtitle: () =>
+ "You can carry on building the app from here on. If you are still not sure, checkout our documentation or try guided tour.",
+};
+
export const ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE = {
bold: () => "Connect your datasource",
- normal: () => "to start building an application.",
+ normal: () => "to start building your app",
};
export const ONBOARDING_CHECKLIST_CREATE_A_QUERY = {
- bold: () => "Create a query",
- normal: () => "of your datasource.",
+ bold: () => "Write a query",
+ normalPrefix: () => "to import your",
+ normal: () => "data into appsmith",
};
export const ONBOARDING_CHECKLIST_ADD_WIDGETS = {
- bold: () => "Start visualising your application",
- normal: () => "using widgets.",
+ bold: () => "Drag & drop a widget,",
+ normal: () => "so you can build a beautiful UI",
};
export const ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET = {
bold: () => "Connect your data to the widgets",
- normal: () => "using JavaScript.",
+ normal: () => "using JavaScript bindings",
};
export const ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS = {
@@ -1007,6 +1016,35 @@ export const ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS = {
normal: () => "and see your creation live.",
};
+export const SIGNPOSTING_LAST_STEP_TOOLTIP = () => "You are almost there!";
+export const SIGNPOSTING_TOOLTIP = {
+ DEFAULT: {
+ content: () =>
+ "Finish these 5 steps to learn the basics in-order to build an app & deploy it. This would take 5 mins of your time.",
+ },
+ CONNECT_A_DATASOURCE: {
+ content: () => "Let's add a datasource",
+ },
+ CREATE_QUERY: {
+ content: () =>
+ "You successfully connected a datasource. Now try to create a query.",
+ },
+ ADD_WIDGET: {
+ content: () =>
+ "You successfully created a query. Now its time to drag & drop a widget to bind data.",
+ },
+ CONNECT_DATA_TO_WIDGET: {
+ content: () =>
+ "You have a widget on the canvas now, its time to bind the data with it.",
+ },
+ DEPLOY_APPLICATION: {
+ content: () => "Deploy you application to see what you’ve built.",
+ },
+ DOCUMENTATION: {
+ content: () => "Open documentation",
+ },
+};
+
export const ONBOARDING_CHECKLIST_FOOTER = () =>
"Not sure where to start? Take the welcome tour";
@@ -1033,6 +1071,9 @@ export const START_TUTORIAL = () => "Start tutorial";
export const WELCOME_TO_APPSMITH = () => "Welcome to Appsmith!";
export const QUERY_YOUR_DATABASE = () =>
"Query your own database or API inside Appsmith. Write JS to construct dynamic queries.";
+export const SIGNPOSTING_INFO_MENU = {
+ documentation: () => "Open documentation",
+};
//Statusbar
export const ONBOARDING_STATUS_STEPS_FIRST = () => "First, add a datasource";
diff --git a/app/client/src/constants/Layers.tsx b/app/client/src/constants/Layers.tsx
index 8ffbd8540c..52d410f40e 100644
--- a/app/client/src/constants/Layers.tsx
+++ b/app/client/src/constants/Layers.tsx
@@ -51,6 +51,7 @@ export const Layers = {
productUpdates: Indices.Layer7,
portals: Indices.Layer9,
header: Indices.Layer9,
+ signpostingOverlay: Indices.Layer9,
snipeableZone: Indices.Layer10,
max: Indices.LayerMax,
sideStickyBar: Indices.Layer7,
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup.tsx
index 0a820319a3..61c4dfdf49 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup.tsx
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/AnonymousDataPopup.tsx
@@ -7,15 +7,54 @@ import {
createMessage,
} from "@appsmith/constants/messages";
import { ADMIN_SETTINGS_CATEGORY_DEFAULT_PATH } from "constants/routes";
-import { TELEMETRY_DOCS_PAGE_URL } from "./constants";
+import {
+ ANONYMOUS_DATA_POPOP_TIMEOUT,
+ TELEMETRY_DOCS_PAGE_URL,
+} from "./constants";
+import { useDispatch, useSelector } from "react-redux";
+import { getCurrentUser } from "selectors/usersSelectors";
+import {
+ getFirstTimeUserOnboardingComplete,
+ getIsAnonymousDataPopupVisible,
+ getIsFirstTimeUserOnboardingEnabled,
+} from "selectors/onboardingSelectors";
+import {
+ getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown,
+ setFirstTimeUserOnboardingTelemetryCalloutVisibility,
+} from "utils/storage";
+import { isAirgapped } from "@appsmith/utils/airgapHelpers";
+import { deleteCanvasCardsState } from "actions/editorActions";
+import styled from "styled-components";
+import { showAnonymousDataPopup } from "actions/onboardingActions";
import AnalyticsUtil from "utils/AnalyticsUtil";
-export default function AnonymousDataPopup(props: {
- onCloseCallout: () => void;
-}) {
+const Wrapper = styled.div`
+ margin: ${(props) =>
+ `${props.theme.spaces[7]}px ${props.theme.spaces[16]}px 0px ${props.theme.spaces[13]}px`};
+`;
+
+export default function AnonymousDataPopup() {
+ const user = useSelector(getCurrentUser);
+ const isAdmin = user?.isSuperUser || false;
+ const isOnboardingCompleted = useSelector(getFirstTimeUserOnboardingComplete);
+ const isAnonymousDataPopupVisible = useSelector(
+ getIsAnonymousDataPopupVisible,
+ );
+ const isFirstTimeUserOnboardingEnabled = useSelector(
+ getIsFirstTimeUserOnboardingEnabled,
+ );
+ const dispatch = useDispatch();
+
+ const hideAnonymousDataPopup = () => {
+ dispatch(showAnonymousDataPopup(false));
+ setFirstTimeUserOnboardingTelemetryCalloutVisibility(true);
+ };
+
useEffect(() => {
- AnalyticsUtil.logEvent("DISPLAY_TELEMETRY_CALLOUT");
- }, []);
+ if (isAnonymousDataPopupVisible) {
+ AnalyticsUtil.logEvent("DISPLAY_TELEMETRY_CALLOUT");
+ }
+ }, [isAnonymousDataPopupVisible]);
const handleLinkClick = (link: string) => {
if (link === ADMIN_SETTINGS_CATEGORY_DEFAULT_PATH) {
@@ -26,8 +65,39 @@ export default function AnonymousDataPopup(props: {
window.open(link, "_blank");
};
+ const showShowAnonymousDataPopup = async () => {
+ const shouldPopupShow =
+ !isAirgapped() &&
+ isFirstTimeUserOnboardingEnabled &&
+ isAdmin &&
+ !isOnboardingCompleted;
+ if (shouldPopupShow) {
+ const isAnonymousDataPopupAlreadyOpen =
+ await getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown();
+ //true if the modal was already shown else show the modal and set to already shown, also hide the modal after 10 secs
+ if (isAnonymousDataPopupAlreadyOpen) {
+ dispatch(showAnonymousDataPopup(false));
+ } else {
+ dispatch(deleteCanvasCardsState());
+ dispatch(showAnonymousDataPopup(true));
+ setTimeout(() => {
+ hideAnonymousDataPopup();
+ }, ANONYMOUS_DATA_POPOP_TIMEOUT);
+ await setFirstTimeUserOnboardingTelemetryCalloutVisibility(true);
+ }
+ } else {
+ dispatch(showAnonymousDataPopup(shouldPopupShow));
+ }
+ };
+
+ useEffect(() => {
+ showShowAnonymousDataPopup();
+ }, []);
+
+ if (!isAnonymousDataPopupVisible) return null;
+
return (
-
+
handleLinkClick(TELEMETRY_DOCS_PAGE_URL),
},
]}
- onClose={() => {
- props.onCloseCallout();
- }}
+ onClose={hideAnonymousDataPopup}
>
{createMessage(ONBOARDING_TELEMETRY_POPUP)}
-
+
);
}
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.test.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.test.tsx
index e4338cc96e..ccf51cb760 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.test.tsx
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.test.tsx
@@ -11,6 +11,7 @@ import { fireEvent, render, screen } from "test/testUtils";
import OnboardingChecklist from "./Checklist";
import { getStore, initialState } from "./testUtils";
import urlBuilder from "entities/URLRedirect/URLAssembly";
+import "@testing-library/jest-dom";
let container: any = null;
@@ -32,6 +33,16 @@ jest.mock("utils/history", () => ({
listen: jest.fn(),
}));
+jest.mock("utils/lazyLottie", () => ({
+ loadAnimation: () => {
+ return {
+ play: jest.fn(),
+ destroy: jest.fn(),
+ goToAndStop: jest.fn(),
+ };
+ },
+}));
+
function renderComponent(store: any) {
render(
@@ -69,20 +80,16 @@ describe("Checklist", () => {
const wrapper = screen.getAllByTestId("checklist-wrapper");
expect(wrapper.length).toBe(1);
const completionInfo = screen.getAllByTestId("checklist-completion-info");
- expect(completionInfo[0].innerHTML).toBe("0 of 5");
- const datasourceButton = screen.getAllByTestId(
- "checklist-datasource-button",
- );
+ expect(completionInfo[0].innerHTML).toBe("0 of 5 ");
+ const datasourceButton = screen.getAllByTestId("checklist-datasource");
expect(datasourceButton.length).toBe(1);
- const actionButton = screen.getAllByTestId("checklist-action-button");
+ const actionButton = screen.getAllByTestId("checklist-action");
expect(actionButton.length).toBe(1);
- const widgetButton = screen.getAllByTestId("checklist-widget-button");
+ const widgetButton = screen.getAllByTestId("checklist-widget");
expect(widgetButton.length).toBe(1);
- const connectionButton = screen.getAllByTestId(
- "checklist-connection-button",
- );
+ const connectionButton = screen.getAllByTestId("checklist-connection");
expect(connectionButton.length).toBe(1);
- const deployButton = screen.getAllByTestId("checklist-deploy-button");
+ const deployButton = screen.getAllByTestId("checklist-deploy");
expect(deployButton.length).toBe(1);
const banner = screen.queryAllByTestId("checklist-completion-banner");
expect(banner.length).toBe(0);
@@ -97,11 +104,9 @@ describe("Checklist", () => {
it("with `add a datasource` task checked off", () => {
renderComponent(getStore(1));
- const datasourceButton = screen.queryAllByTestId(
- "checklist-datasource-button",
- );
- expect(datasourceButton.length).toBe(0);
- const actionButton = screen.queryAllByTestId("checklist-action-button");
+ const datasourceButton = screen.queryAllByTestId("checklist-datasource");
+ expect(datasourceButton[0]).toHaveStyle("cursor: auto");
+ const actionButton = screen.queryAllByTestId("checklist-action");
fireEvent.click(actionButton[0]);
expect(history).toHaveBeenCalledWith(
integrationEditorURL({
@@ -113,9 +118,9 @@ describe("Checklist", () => {
it("with `add a query` task checked off", () => {
renderComponent(getStore(2));
- const actionButton = screen.queryAllByTestId("checklist-action-button");
- expect(actionButton.length).toBe(0);
- const widgetButton = screen.queryAllByTestId("checklist-widget-button");
+ const actionButton = screen.queryAllByTestId("checklist-action");
+ expect(actionButton[0]).toHaveStyle("cursor: auto");
+ const widgetButton = screen.queryAllByTestId("checklist-widget");
fireEvent.click(widgetButton[0]);
expect(history).toHaveBeenCalledWith(
builderURL({ pageId: initialState.entities.pageList.currentPageId }),
@@ -133,11 +138,9 @@ describe("Checklist", () => {
it("with `add a widget` task checked off", () => {
const store: any = getStore(3);
renderComponent(store);
- const widgetButton = screen.queryAllByTestId("checklist-widget-button");
- expect(widgetButton.length).toBe(0);
- const connectionButton = screen.queryAllByTestId(
- "checklist-connection-button",
- );
+ const widgetButton = screen.queryAllByTestId("checklist-widget");
+ expect(widgetButton[0]).toHaveStyle("cursor: auto");
+ const connectionButton = screen.queryAllByTestId("checklist-connection");
fireEvent.click(connectionButton[0]);
expect(dispatch).toHaveBeenCalledWith(
bindDataOnCanvas({
@@ -151,11 +154,9 @@ describe("Checklist", () => {
it("with `connect your data` task checked off", () => {
useIsWidgetActionConnectionPresent = true;
renderComponent(getStore(4));
- const connectionButton = screen.queryAllByTestId(
- "checklist-connection-button",
- );
- expect(connectionButton.length).toBe(0);
- const deployButton = screen.queryAllByTestId("checklist-deploy-button");
+ const connectionButton = screen.queryAllByTestId("checklist-connection");
+ expect(connectionButton[0]).toHaveStyle("cursor: auto");
+ const deployButton = screen.queryAllByTestId("checklist-deploy");
fireEvent.click(deployButton[0]);
expect(dispatch).toHaveBeenCalledWith({
type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx
index 64b5319c5c..59938109ca 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Checklist.tsx
@@ -1,139 +1,162 @@
-import React from "react";
-import { Text, TextType } from "design-system-old";
-import { Button, Icon, Link } from "design-system";
+import React, { useEffect, useRef } from "react";
+import { Button, Divider, Text, Tooltip } from "design-system";
import styled from "styled-components";
import { useDispatch, useSelector } from "react-redux";
import {
getCanvasWidgets,
- getDatasources,
getPageActions,
+ getSavedDatasources,
} from "selectors/entitiesSelector";
import { useIsWidgetActionConnectionPresent } from "pages/Editor/utils";
import { getEvaluationInverseDependencyMap } from "selectors/dataTreeSelectors";
-import { APPLICATIONS_URL, INTEGRATION_TABS } from "constants/routes";
+import { INTEGRATION_TABS } from "constants/routes";
import {
getApplicationLastDeployedAt,
getCurrentApplicationId,
getCurrentPageId,
} from "selectors/editorSelectors";
import history from "utils/history";
-import { toggleInOnboardingWidgetSelection } from "actions/onboardingActions";
+import {
+ setSignpostingOverlay,
+ showSignpostingModal,
+ showSignpostingTooltip,
+ signpostingMarkAllRead,
+ toggleInOnboardingWidgetSelection,
+} from "actions/onboardingActions";
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import {
getFirstTimeUserOnboardingComplete,
- getIsFirstTimeUserOnboardingEnabled,
+ getSignpostingStepStateByStep,
} from "selectors/onboardingSelectors";
import AnalyticsUtil from "utils/AnalyticsUtil";
import { forceOpenWidgetPanel } from "actions/widgetSidebarActions";
import { bindDataOnCanvas } from "actions/pluginActionActions";
-import { Redirect } from "react-router";
import {
ONBOARDING_CHECKLIST_ACTIONS,
- ONBOARDING_CHECKLIST_BANNER_BODY,
- ONBOARDING_CHECKLIST_BANNER_HEADER,
ONBOARDING_CHECKLIST_HEADER,
- ONBOARDING_CHECKLIST_BODY,
- ONBOARDING_CHECKLIST_COMPLETE_TEXT,
ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE,
ONBOARDING_CHECKLIST_CREATE_A_QUERY,
ONBOARDING_CHECKLIST_ADD_WIDGETS,
ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET,
ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS,
- ONBOARDING_CHECKLIST_FOOTER,
- ONBOARDING_CHECKLIST_BANNER_BUTTON,
createMessage,
+ SIGNPOSTING_POPUP_SUBTITLE,
+ SIGNPOSTING_SUCCESS_POPUP,
+ SIGNPOSTING_TOOLTIP,
} from "@appsmith/constants/messages";
import type { Datasource } from "entities/Datasource";
import type { ActionDataState } from "reducers/entityReducers/actionsReducer";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
-import { triggerWelcomeTour } from "./Utils";
+import { SIGNPOSTING_STEP } from "./Utils";
import { builderURL, integrationEditorURL } from "RouteBuilder";
-import { isAirgapped } from "@appsmith/utils/airgapHelpers";
import { DatasourceCreateEntryPoints } from "constants/Datasource";
+import classNames from "classnames";
+import lazyLottie from "utils/lazyLottie";
+import tickMarkAnimationURL from "assets/lottie/guided-tour-tick-mark.json.txt";
+import { getAppsmithConfigs } from "@appsmith/configs";
+const { intercomAppID } = getAppsmithConfigs();
-const Wrapper = styled.div`
- padding: var(--ads-v2-spaces-7);
- background: #fff;
- height: calc(100vh - ${(props) => props.theme.smallHeaderHeight});
- overflow: auto;
+const StyledDivider = styled(Divider)`
+ display: block;
`;
-const Pageheader = styled.h4`
- font-size: ${(props) => props.theme.fontSizes[6]}px;
+const PrefixCircle = styled.div<{ disabled: boolean }>`
+ height: 13px;
+ width: 13px;
+ border-radius: 50%;
+ border: 1px solid
+ ${(props) =>
+ !props.disabled
+ ? "var(--ads-v2-color-bg-brand-secondary)"
+ : "var(--ads-v2-color-fg-subtle)"};
`;
-const PageSubHeader = styled.p`
- width: 100%;
- margin-bottom: ${(props) => props.theme.spaces[12]}px;
+const LottieAnimationContainer = styled.div`
+ height: 36px;
+ width: 36px;
+ left: -12px;
+ top: -13px;
+ position: absolute;
`;
-const StatusWrapper = styled.p`
- width: 100%;
- margin-bottom: ${(props) => props.theme.spaces[12]}px;
- & span {
- font-weight: 700;
- }
+const LottieAnimationWrapper = styled.div`
+ height: 13px;
+ width: 13px;
+ position: relative;
`;
-const StyledList = styled.ul`
- margin: 0;
- padding: 0;
- list-style-type: none;
- overflow: auto;
-`;
-
-const StyledListItem = styled.li`
- width: 100%;
- display: flex;
- padding: var(--ads-v2-spaces-7) 0px;
- align-items: center;
- justify-content: space-between;
- border-bottom: 1px solid var(--ads-v2-color-border);
- &:first-child {
- border-top: 1px solid var(--ads-v2-color-border);
- }
-`;
-const StyledListItemTextWrapper = styled.div`
- display: flex;
- align-items: center;
- flex: 1;
-`;
-const CHECKLIST_WIDTH_OFFSET = 268;
-
-const ChecklistText = styled.div<{ active: boolean }>`
- flex-basis: calc(100% - ${CHECKLIST_WIDTH_OFFSET}px);
- & span {
- font-weight: 700;
- }
-`;
-
-const StyledCompleteMarker = styled.div`
- flex-basis: 40px;
-`;
-
-const Banner = styled.div`
+const ListItem = styled.div<{ disabled: boolean; completed: boolean }>`
border-radius: var(--ads-v2-border-radius);
- border: 1px solid var(--ads-v2-color-border);
- padding: var(--ads-v2-spaces-5);
- margin-top: var(--ads-v2-spaces-7);
+ position: relative;
+ cursor: ${(props) => {
+ if (props.disabled) {
+ return "not-allowed";
+ } else if (props.completed) {
+ return "auto";
+ }
+
+ return "pointer;";
+ }};
+
+ // Strikethrought animation
+ .signposting-strikethrough {
+ position: relative;
+ }
+ .signposting-strikethrough-static {
+ text-decoration: line-through;
+ }
+ .signposting-strikethrough:after {
+ content: " ";
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 0;
+ height: 1px;
+ background: black;
+ animation-duration: 2s;
+ animation-fill-mode: forwards;
+ }
+ .signposting-strikethrough::after {
+ -webkit-animation-name: bounceInLeft;
+ animation-name: bounceInLeft;
+ }
+ .signposting-strikethrough-bold::after {
+ -webkit-animation-name: signposting-strikethrough-bold;
+ animation-name: signposting-strikethrough-bold;
+ }
+ .signposting-strikethrough-normal::after {
+ -webkit-animation-name: signposting-strikethrough-normal;
+ animation-name: signposting-strikethrough-normal;
+ }
+ @keyframes signposting-strikethrough-bold {
+ 0% {
+ width: 0;
+ }
+ 50% {
+ width: 100%;
+ }
+ 100% {
+ width: 100%;
+ }
+ }
+ @keyframes signposting-strikethrough-normal {
+ 30% {
+ width: 0;
+ }
+ 100% {
+ width: 100%;
+ }
+ }
`;
-const BannerHeader = styled.h5`
- font-size: 20px;
- margin: 0;
-`;
-
-const BannerText = styled.p`
- margin: ${(props) => props.theme.spaces[3]}px 0px
- ${(props) => props.theme.spaces[7]}px;
-`;
-
-const StyledFooter = styled.div`
- cursor: pointer;
- display: flex;
- align-items: center;
- margin-top: ${(props) => props.theme.spaces[9]}px;
- margin-bottom: ${(props) => props.theme.spaces[9]}px;
+const Sibling = styled.div<{ disabled: boolean }>`
+ border-radius: var(--ads-v2-border-radius);
+ &:hover {
+ background-color: ${(props) =>
+ !props.disabled ? "var(--ads-v2-color-bg-subtle)" : "transparent"};
+ }
+ padding: var(--ads-v2-spaces-3);
+ padding-right: var(--ads-v2-spaces-2);
`;
function getSuggestedNextActionAndCompletedTasks(
@@ -186,10 +209,147 @@ function getSuggestedNextActionAndCompletedTasks(
return { suggestedNextAction, completedTasks };
}
+function CheckListItem(props: {
+ boldText: string;
+ normalPrefixText?: string;
+ normalText: string;
+ onClick: () => void;
+ disabled: boolean;
+ completed: boolean;
+ step: SIGNPOSTING_STEP;
+ docLink?: string;
+ testid: string;
+}) {
+ const stepState = useSelector((state) =>
+ getSignpostingStepStateByStep(state, props.step),
+ );
+ const tickMarkRef = useRef(null);
+ useEffect(() => {
+ if (props.completed) {
+ const anim = lazyLottie.loadAnimation({
+ path: tickMarkAnimationURL,
+ container: tickMarkRef?.current as HTMLDivElement,
+ renderer: "svg",
+ loop: false,
+ autoplay: false,
+ });
+ if (!stepState?.read) {
+ anim.play();
+ } else {
+ // We want to show animation only for the first time. Once completed we show the last frame.
+ // Here 60 is the last frame
+ anim.goToAndStop(60, true);
+ }
+
+ return () => {
+ anim.destroy();
+ };
+ }
+ }, [tickMarkRef?.current, props.completed, stepState?.read]);
+
+ return (
+
+
null
+ : () => {
+ props.onClick();
+ }
+ }
+ >
+
+ {props.completed ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ {props.boldText}
+ {props.normalPrefixText && (
+
+ {props.normalPrefixText}
+
+ )}
+
+
+
+ {props.normalText}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
export default function OnboardingChecklist() {
- const isAirgappedInstance = isAirgapped();
const dispatch = useDispatch();
- const datasources = useSelector(getDatasources);
+ const datasources = useSelector(getSavedDatasources);
const pageId = useSelector(getCurrentPageId);
const actions = useSelector(getPageActions(pageId));
const widgets = useSelector(getCanvasWidgets);
@@ -199,26 +359,21 @@ export default function OnboardingChecklist() {
actions,
deps,
);
- // const theme = useSelector(getCurrentThemeDetails);
const applicationId = useSelector(getCurrentApplicationId);
const isDeployed = !!useSelector(getApplicationLastDeployedAt);
- const isCompleted = useSelector(getFirstTimeUserOnboardingComplete);
- const isFirstTimeUserOnboardingEnabled = useSelector(
- getIsFirstTimeUserOnboardingEnabled,
+ const { completedTasks } = getSuggestedNextActionAndCompletedTasks(
+ datasources,
+ actions,
+ widgets,
+ isConnectionPresent,
+ isDeployed,
+ );
+ const isFirstTimeUserOnboardingComplete = useSelector(
+ getFirstTimeUserOnboardingComplete,
);
- if (!isFirstTimeUserOnboardingEnabled && !isCompleted) {
- return ;
- }
- const { completedTasks, suggestedNextAction } =
- getSuggestedNextActionAndCompletedTasks(
- datasources,
- actions,
- widgets,
- isConnectionPresent,
- isDeployed,
- );
const onconnectYourWidget = () => {
const action = actions[0];
+ dispatch(showSignpostingModal(false));
if (action && applicationId && pageId) {
dispatch(
bindDataOnCanvas({
@@ -230,311 +385,219 @@ export default function OnboardingChecklist() {
} else {
history.push(builderURL({ pageId }));
}
- AnalyticsUtil.logEvent("SIGNPOSTING_CONNECT_WIDGET_CLICK");
+ AnalyticsUtil.logEvent("SIGNPOSTING_MODAL_CONNECT_WIDGET_CLICK");
};
- return (
-
- history.push(builderURL({ pageId }))}
- startIcon="back-control"
- >
- Back
-
- {isCompleted && (
-
-
- {createMessage(ONBOARDING_CHECKLIST_BANNER_HEADER)}
-
-
- {createMessage(ONBOARDING_CHECKLIST_BANNER_BODY)}
-
-
-
- )}
-
- {createMessage(ONBOARDING_CHECKLIST_HEADER)}
-
- {createMessage(ONBOARDING_CHECKLIST_BODY)}
-
- {
+ if (intercomAppID && window.Intercom) {
+ // Close signposting modal when intercom modal is open
+ window.Intercom("onShow", () => {
+ dispatch(showSignpostingModal(false));
+ });
+ }
+
+ return () => {
+ dispatch(signpostingMarkAllRead());
+ dispatch(setSignpostingOverlay(false));
+ dispatch(showSignpostingTooltip(false));
+ };
+ }, []);
+
+ // End signposting for the application once signposting is complete and the
+ // signposting complete menu is closed
+ useEffect(() => {
+ return () => {
+ if (isFirstTimeUserOnboardingComplete) {
+ dispatch({
+ type: ReduxActionTypes.END_FIRST_TIME_USER_ONBOARDING,
+ });
+ }
+ };
+ }, [isFirstTimeUserOnboardingComplete]);
+
+ // Success UI
+ if (isFirstTimeUserOnboardingComplete) {
+ return (
+ <>
+
- {completedTasks} of 5
-
- {createMessage(ONBOARDING_CHECKLIST_COMPLETE_TEXT)}
-
-
-
-
-
-
-
-
-
- {createMessage(ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.bold)}
-
-
- {createMessage(ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.normal)}
-
-
- {!datasources.length && !actions.length && (
-
- )}
-
-
-
-
-
-
-
-
- {createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.bold)}
-
- {createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.normal)}
-
-
- {!actions.length && (
-
- )}
-
-
-
-
- 1
- ? "var(--ads-v2-color-fg-success)"
- : ""
- }
- data-testid="checklist-widget-complete-icon"
- name="oval-check"
- size="lg"
- />
-
- 1}>
-
- {createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.bold)}
-
- {createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.normal)}
-
-
- {Object.keys(widgets).length === 1 && (
-
- )}
-
-
-
-
-
-
-
-
- {createMessage(
- ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.bold,
- )}
-
-
- {createMessage(
- ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.normal,
- )}
-
-
- {!isConnectionPresent && (
-
- )}
-
-
-
-
-
-
-
-
- {createMessage(ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.bold)}
-
-
- {createMessage(ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.normal)}
-
-
- {!isDeployed && (
-
- )}
-
-
- {!isAirgappedInstance && (
- triggerWelcomeTour(dispatch, applicationId)}
- >
-
-
-
-
- {createMessage(ONBOARDING_CHECKLIST_FOOTER)}
+
+ {createMessage(SIGNPOSTING_SUCCESS_POPUP.title)}
-
-
- )}
-
+
+
+ {createMessage(SIGNPOSTING_SUCCESS_POPUP.subtitle)}
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+ {createMessage(ONBOARDING_CHECKLIST_HEADER)}
+
+
+
+ {createMessage(SIGNPOSTING_POPUP_SUBTITLE)}
+
+
+
+ {completedTasks} of 5{" "}
+
+ complete
+
+
+
+
+ {
+ AnalyticsUtil.logEvent(
+ "SIGNPOSTING_MODAL_CREATE_DATASOURCE_CLICK",
+ {
+ from: "CHECKLIST",
+ },
+ );
+ dispatch(showSignpostingModal(false));
+ history.push(
+ integrationEditorURL({
+ pageId,
+ selectedTab: INTEGRATION_TABS.NEW,
+ }),
+ );
+ }}
+ step={SIGNPOSTING_STEP.CONNECT_A_DATASOURCE}
+ testid={"checklist-datasource"}
+ />
+ {
+ AnalyticsUtil.logEvent("SIGNPOSTING_MODAL_CREATE_QUERY_CLICK", {
+ from: "CHECKLIST",
+ });
+ dispatch(showSignpostingModal(false));
+ history.push(
+ integrationEditorURL({
+ pageId,
+ selectedTab: INTEGRATION_TABS.ACTIVE,
+ }),
+ );
+ // Event for datasource creation click
+ const entryPoint = DatasourceCreateEntryPoints.NEW_APP_CHECKLIST;
+ AnalyticsUtil.logEvent("NAVIGATE_TO_CREATE_NEW_DATASOURCE_PAGE", {
+ entryPoint,
+ });
+ }}
+ step={SIGNPOSTING_STEP.CREATE_A_QUERY}
+ testid={"checklist-action"}
+ />
+ 1}
+ disabled={false}
+ docLink="https://docs.appsmith.com/reference/widgets"
+ normalText={createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.normal)}
+ onClick={() => {
+ AnalyticsUtil.logEvent("SIGNPOSTING_MODAL_ADD_WIDGET_CLICK", {
+ from: "CHECKLIST",
+ });
+ dispatch(showSignpostingModal(false));
+ dispatch(toggleInOnboardingWidgetSelection(true));
+ dispatch(forceOpenWidgetPanel(true));
+ history.push(builderURL({ pageId }));
+ }}
+ step={SIGNPOSTING_STEP.ADD_WIDGETS}
+ testid={"checklist-widget"}
+ />
+
+ {
+ AnalyticsUtil.logEvent("SIGNPOSTING_MODAL_PUBLISH_CLICK", {
+ from: "CHECKLIST",
+ });
+ dispatch(showSignpostingModal(false));
+ dispatch({
+ type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
+ payload: {
+ applicationId,
+ },
+ });
+ }}
+ step={SIGNPOSTING_STEP.DEPLOY_APPLICATIONS}
+ testid={"checklist-deploy"}
+ />
+
+ >
);
}
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/HelpMenu.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/HelpMenu.tsx
new file mode 100644
index 0000000000..cf77d597a4
--- /dev/null
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/HelpMenu.tsx
@@ -0,0 +1,140 @@
+import React from "react";
+import { Text, Button } from "design-system";
+import { getAppsmithConfigs } from "@appsmith/configs";
+import {
+ APPSMITH_DISPLAY_VERSION,
+ createMessage,
+} from "@appsmith/constants/messages";
+import moment from "moment";
+import styled from "styled-components";
+import { triggerWelcomeTour } from "./Utils";
+import { useDispatch, useSelector } from "react-redux";
+import { getCurrentUser } from "selectors/usersSelectors";
+import { IntercomConsent } from "../HelpButton";
+import classNames from "classnames";
+import AnalyticsUtil from "utils/AnalyticsUtil";
+const { appVersion, cloudHosting, intercomAppID } = getAppsmithConfigs();
+
+type HelpItem = {
+ label: string;
+ link?: string;
+ id?: string;
+ icon: string;
+};
+const HELP_MENU_ITEMS: HelpItem[] = [
+ {
+ icon: "book-line",
+ label: "Documentation",
+ link: "https://docs.appsmith.com/",
+ },
+ {
+ icon: "bug-line",
+ label: "Report a bug",
+ link: "https://github.com/appsmithorg/appsmith/issues/new/choose",
+ },
+];
+
+if (intercomAppID && window.Intercom) {
+ HELP_MENU_ITEMS.push({
+ icon: "chat-help",
+ label: "Chat with us",
+ id: "intercom-trigger",
+ });
+}
+
+const StyledText = styled(Text)`
+ font-size: 8px;
+ font-weight: normal;
+`;
+
+const HelpFooter = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+`;
+
+function HelpMenu(props: {
+ setShowIntercomConsent: (val: boolean) => void;
+ showIntercomConsent: boolean;
+}) {
+ const dispatch = useDispatch();
+ const user = useSelector(getCurrentUser);
+
+ return (
+
+ {props.showIntercomConsent ? (
+
+ ) : (
+ <>
+
+ Help & Resources
+
+
+
+ {HELP_MENU_ITEMS.map((item) => {
+ return (
+
+ );
+ })}
+
+ >
+ )}
+ {appVersion.id && (
+
+
+ {createMessage(
+ APPSMITH_DISPLAY_VERSION,
+ appVersion.edition,
+ appVersion.id,
+ cloudHosting,
+ )}
+
+
+ Released {moment(appVersion.releaseDate).fromNow()}
+
+
+ )}
+
+ );
+}
+
+export default HelpMenu;
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/IntroductionModal.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/IntroductionModal.tsx
deleted file mode 100644
index dfa5c51b2c..0000000000
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/IntroductionModal.tsx
+++ /dev/null
@@ -1,226 +0,0 @@
-import {
- Button,
- Modal,
- ModalBody,
- ModalContent,
- ModalFooter,
- ModalHeader,
-} from "design-system";
-import {
- HOW_APPSMITH_WORKS,
- BUILD_MY_FIRST_APP,
- createMessage,
- WELCOME_TO_APPSMITH,
- ONBOARDING_INTRO_CONNECT_YOUR_DATABASE,
- QUERY_YOUR_DATABASE,
- DRAG_AND_DROP,
- CUSTOMIZE_WIDGET_STYLING,
- ONBOARDING_INTRO_PUBLISH,
- CHOOSE_ACCESS_CONTROL_ROLES,
- ONBOARDING_INTRO_FOOTER,
- START_TUTORIAL,
-} from "@appsmith/constants/messages";
-import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
-import React from "react";
-import { useEffect } from "react";
-import { useDispatch, useSelector } from "react-redux";
-import styled from "styled-components";
-import AnalyticsUtil from "utils/AnalyticsUtil";
-import { triggerWelcomeTour } from "./Utils";
-import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
-import { getCurrentApplicationId } from "selectors/editorSelectors";
-import { getAssetUrl, isAirgapped } from "@appsmith/utils/airgapHelpers";
-
-const ModalSubHeader = styled.h5`
- font-size: 14px;
-`;
-
-const ModalContentWrapper = styled.div``;
-const ModalContentRow = styled.div<{ border?: boolean }>`
- flex-direction: row;
- display: flex;
- justify-content: space-between;
- height: 113px;
- padding: 20px 0px;
- ${(props) =>
- props.border ? "border-bottom: 1px solid var(--ads-v2-color-border);" : ""}
-`;
-const ModalContentTextWrapper = styled.div`
- display: flex;
- align-items: center;
- flex: 3;
-`;
-
-const StyledImgWrapper = styled.div`
- display: flex;
- flex: 1;
- justify-content: center;
-`;
-
-const StyledImg = styled.img`
- vertical-align: middle;
-`;
-
-const StyledCount = styled.h5`
- font-size: 16px;
- font-weight: 500;
- color: var(--ads-v2-color-fg-emphasis);
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background-color: var(--ads-v2-color-bg-muted);
- display: flex;
- align-items: center;
- justify-content: center;
-`;
-
-const ModalContentItem = styled.div`
- margin-left: 36px;
-`;
-const ModalContentHeader = styled.h5`
- font-size: 16px;
- font-weight: 500;
-`;
-const ModalContentDescription = styled.h5`
- font-size: 14px;
-`;
-
-const ModalFooterText = styled.span`
- font-size: 14px;
- letter-spacing: -0.24px;
-`;
-
-type IntroductionModalProps = {
- close: () => void;
-};
-
-const getConnectDataImg = () => `${ASSETS_CDN_URL}/ConnectData-v2.svg`;
-const getDragAndDropImg = () => `${ASSETS_CDN_URL}/DragAndDrop.svg`;
-const getPublishAppsImg = () => `${ASSETS_CDN_URL}/PublishApps-v2.svg`;
-
-export default function IntroductionModal({ close }: IntroductionModalProps) {
- const modalAlwaysOpen = true;
- const dispatch = useDispatch();
- const applicationId = useSelector(getCurrentApplicationId);
- const isAirgappedInstance = isAirgapped();
- const onBuildApp = () => {
- AnalyticsUtil.logEvent("SIGNPOSTING_BUILD_APP_CLICK");
- close();
- };
-
- useEffect(() => {
- dispatch({
- type: ReduxActionTypes.GET_ALL_APPLICATION_INIT,
- });
- }, []);
-
- const closeModal = (isOpen: boolean) => {
- if (!isOpen) {
- onBuildApp();
- }
- };
-
- return (
-
- e.preventDefault()}
- onInteractOutside={(e) => e.preventDefault()}
- style={{ width: "920px" }}
- >
-
- {createMessage(WELCOME_TO_APPSMITH)}
-
-
- {createMessage(HOW_APPSMITH_WORKS)}
-
-
-
-
- 1
-
-
-
- {createMessage(ONBOARDING_INTRO_CONNECT_YOUR_DATABASE)}
-
-
- {createMessage(QUERY_YOUR_DATABASE)}
-
-
-
-
-
-
-
-
-
-
- 2
-
-
-
- {createMessage(DRAG_AND_DROP)}
-
-
- {createMessage(CUSTOMIZE_WIDGET_STYLING)}
-
-
-
-
-
-
-
-
-
-
- 3
-
-
-
- {createMessage(ONBOARDING_INTRO_PUBLISH)}
-
-
- {createMessage(CHOOSE_ACCESS_CONTROL_ROLES)}
-
-
-
-
-
-
-
-
-
- {createMessage(ONBOARDING_INTRO_FOOTER)}
-
-
-
- {!isAirgappedInstance && (
-
- )}
-
-
-
-
- );
-}
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx
new file mode 100644
index 0000000000..0ec2858603
--- /dev/null
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Modal.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+import { MenuContent } from "design-system";
+import styled from "styled-components";
+import Checklist from "./Checklist";
+import HelpMenu from "./HelpMenu";
+import { useDispatch } from "react-redux";
+import { showSignpostingModal } from "actions/onboardingActions";
+
+const SIGNPOSTING_POPUP_WIDTH = "360px";
+
+const StyledMenuContent = styled(MenuContent)<{ animate: boolean }>`
+ max-width: ${SIGNPOSTING_POPUP_WIDTH};
+ overflow: hidden;
+ display: flex;
+ animation-name: slideUpAndFade;
+ @keyframes slideUpAndFade {
+ from {
+ opacity: 0;
+ transform: translateY(2px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+`;
+const Wrapper = styled.div`
+ padding: var(--ads-v2-spaces-4) var(--ads-v2-spaces-5);
+ display: flex;
+ flex-direction: column;
+`;
+
+function OnboardingModal(props: {
+ setOverlay: boolean;
+ showIntercomConsent: boolean;
+ setShowIntercomConsent: (val: boolean) => void;
+}) {
+ const dispatch = useDispatch();
+
+ return (
+ {
+ dispatch(showSignpostingModal(false));
+ }}
+ width={SIGNPOSTING_POPUP_WIDTH}
+ >
+
+ {!props.showIntercomConsent && }
+
+
+
+ );
+}
+
+export default OnboardingModal;
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Overlay.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Overlay.tsx
new file mode 100644
index 0000000000..6f6ceced64
--- /dev/null
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Overlay.tsx
@@ -0,0 +1,46 @@
+import { showSignpostingModal } from "actions/onboardingActions";
+import { Layers } from "constants/Layers";
+import React from "react";
+import { useDispatch, useSelector } from "react-redux";
+import {
+ getIsFirstTimeUserOnboardingEnabled,
+ getSignpostingSetOverlay,
+} from "selectors/onboardingSelectors";
+import styled from "styled-components";
+
+const StyledOverlay = styled.div`
+ z-index: ${Layers.signpostingOverlay};
+ opacity: 0.6;
+ background-color: transparent;
+ animation: fade-in 1s forwards;
+ will-change: background-color;
+ @keyframes fade-in {
+ from {
+ background-color: transparent;
+ }
+ to {
+ background-color: var(--ads-v2-color-bg-emphasis-max);
+ }
+ }
+`;
+
+function Overlay() {
+ const signpostingEnabled = useSelector(getIsFirstTimeUserOnboardingEnabled);
+ const setOverlay = useSelector(getSignpostingSetOverlay);
+ const dispatch = useDispatch();
+
+ if (signpostingEnabled && setOverlay) {
+ return (
+ {
+ dispatch(showSignpostingModal(false));
+ }}
+ />
+ );
+ }
+
+ return null;
+}
+
+export default Overlay;
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.test.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.test.tsx
index 3e7642e26b..c8351741e6 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.test.tsx
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.test.tsx
@@ -1,20 +1,25 @@
+const dispatch = jest.fn();
+
import React from "react";
import { Provider } from "react-redux";
-import { render, screen } from "test/testUtils";
+import { render } from "test/testUtils";
import OnboardingStatusbar from "./Statusbar";
import { getStore } from "./testUtils";
-import {
- ONBOARDING_STATUS_STEPS_FIRST,
- ONBOARDING_STATUS_STEPS_SECOND,
- ONBOARDING_STATUS_STEPS_THIRD,
- ONBOARDING_STATUS_STEPS_FOURTH,
- ONBOARDING_STATUS_STEPS_FIVETH,
- ONBOARDING_STATUS_STEPS_SIXTH,
-} from "@appsmith/constants/messages";
import { useIsWidgetActionConnectionPresent } from "pages/Editor/utils";
+import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
+import { SIGNPOSTING_STEP } from "./Utils";
+import { signpostingStepUpdateInit } from "actions/onboardingActions";
let container: any = null;
+jest.mock("react-redux", () => {
+ const originalModule = jest.requireActual("react-redux");
+ return {
+ ...originalModule,
+ useDispatch: () => dispatch,
+ };
+});
+
function renderComponent(store: any) {
render(
@@ -30,52 +35,77 @@ describe("Statusbar", () => {
document.body.appendChild(container);
});
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
it("is rendered", async () => {
renderComponent(getStore(0));
- const statusbar = screen.queryAllByTestId("statusbar-container");
- expect(statusbar).toHaveLength(1);
+ expect(dispatch).toHaveBeenCalledTimes(5);
+ expect(dispatch).toHaveBeenCalledWith(
+ expect.objectContaining({
+ payload: {
+ completed: false,
+ step: expect.any(String),
+ },
+ type: ReduxActionTypes.SIGNPOSTING_STEP_UPDATE_INIT,
+ }),
+ );
});
- it("is pro", async () => {
- renderComponent(getStore(0));
- const statusbar = screen.queryAllByTestId("statusbar-container");
- expect(statusbar).not.toBeNull();
- });
-
- it("is showing first step", async () => {
- renderComponent(getStore(0));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_FIRST());
- });
-
- it("is showing second step", async () => {
+ it("on completing first step", async () => {
renderComponent(getStore(1));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_SECOND());
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 1,
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CONNECT_A_DATASOURCE,
+ completed: true,
+ }),
+ );
});
- it("is showing third step", async () => {
+ it("on completing second step", async () => {
renderComponent(getStore(2));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_THIRD());
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 2,
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CREATE_A_QUERY,
+ completed: true,
+ }),
+ );
});
- it("is showing fourth step", async () => {
+ it("on completing third step", async () => {
renderComponent(getStore(3));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_FOURTH());
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 3,
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.ADD_WIDGETS,
+ completed: true,
+ }),
+ );
});
- it("is showing fifth step", async () => {
+ it("on completing fourth step", async () => {
renderComponent(getStore(4));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_FIVETH());
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 4,
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CONNECT_DATA_TO_WIDGET,
+ completed: true,
+ }),
+ );
});
- it("is showing sixth step", async () => {
+ it("on completing fifth step", async () => {
renderComponent(getStore(5));
- const statusbarText = screen.queryAllByTestId("statusbar-text");
- expect(statusbarText[0].innerHTML).toBe(ONBOARDING_STATUS_STEPS_SIXTH());
+ expect(dispatch).toHaveBeenNthCalledWith(
+ 5,
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.DEPLOY_APPLICATIONS,
+ completed: true,
+ }),
+ );
});
it("should test useIsWidgetActionConnectionPresent function", () => {
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.tsx
index 9ff66b1ca5..b38aee91d4 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.tsx
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Statusbar.tsx
@@ -1,10 +1,6 @@
-import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { useIsWidgetActionConnectionPresent } from "pages/Editor/utils";
-import type { SyntheticEvent } from "react";
-import React from "react";
+import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
-import type { RouteComponentProps } from "react-router-dom";
-import { withRouter } from "react-router-dom";
import { getEvaluationInverseDependencyMap } from "selectors/dataTreeSelectors";
import {
getApplicationLastDeployedAt,
@@ -12,118 +8,16 @@ import {
} from "selectors/editorSelectors";
import {
getCanvasWidgets,
- getDatasources,
getPageActions,
+ getSavedDatasources,
} from "selectors/entitiesSelector";
-import {
- getFirstTimeUserOnboardingComplete,
- getInOnboardingWidgetSelection,
-} from "selectors/onboardingSelectors";
-import styled from "styled-components";
-import history from "utils/history";
-import {
- ONBOARDING_STATUS_STEPS_FIRST,
- ONBOARDING_STATUS_STEPS_FIRST_ALT,
- ONBOARDING_STATUS_STEPS_SECOND,
- ONBOARDING_STATUS_STEPS_THIRD,
- ONBOARDING_STATUS_STEPS_FOURTH,
- ONBOARDING_STATUS_STEPS_FIVETH,
- ONBOARDING_STATUS_STEPS_SIXTH,
- ONBOARDING_STATUS_GET_STARTED,
- createMessage,
- ONBOARDING_STATUS_STEPS_THIRD_ALT,
-} from "@appsmith/constants/messages";
-import { getTypographyByKey } from "design-system-old";
-import { onboardingCheckListUrl } from "RouteBuilder";
-import { Icon, Button } from "design-system";
+import { SIGNPOSTING_STEP } from "./Utils";
+import { getFirstTimeUserOnboardingComplete } from "selectors/onboardingSelectors";
+import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
+import { signpostingStepUpdateInit } from "actions/onboardingActions";
-const Wrapper = styled.div<{ active: boolean }>`
- width: 100%;
- background-color: ${(props) =>
- props.active ? "var(--ads-v2-color-bg-brand)" : ""};
- cursor: ${(props) => (props.active ? "default" : "pointer")};
- height: ${(props) => props.theme.onboarding.statusBarHeight}px;
- padding: 12px 16px;
- transition: background-color 0.3s ease;
- border-bottom: 1px solid var(--ads-v2-color-border);
-
- ${(props) =>
- props.active &&
- `
- p {
- color: var(--ads-v2-color-fg-on-brand);
- }
- svg, svg path {
- fill: var(--ads-v2-color-fg-on-brand) !important;
- }
- `}
-
- &:hover .hover-icons {
- opacity: 1;
- }
-`;
-
-const TitleWrapper = styled.p`
- ${getTypographyByKey("p4")}
- color: var(--ads-v2-color-fg);
-`;
-
-const StatusText = styled.p`
- font-size: 13px;
- display: flex;
- & .hover-icons {
- transform: translate(3px, 0px);
- opacity: 0;
- }
-`;
-
-const ProgressContainer = styled.div`
- background-color: ${(props) =>
- props.active
- ? "var(--ads-v2-color-bg-brand-emphasis-plus)"
- : "var(--ads-v2-color-bg-subtle)"};
- border-radius: var(--ads-v2-border-radius);
- overflow: hidden;
- margin-top: 12px;
-`;
-
-const Progressbar = styled.div`
- width: ${(props) => props.percentage}%;
- height: 6px;
- background: ${(props) =>
- props.active
- ? "var(--ads-v2-color-bg)"
- : "var(--ads-v2-color-bg-brand-emphasis-plus)"};
- transition: width 0.3s ease, background 0.3s ease;
- border-radius: var(--ads-v2-border-radius);
-`;
-
-const StyledClose = styled(Button)`
- position: absolute !important;
- top: 9px;
- right: 9px;
- opacity: 0;
- cursor: pointer;
-`;
-
-type StatusProgressbarType = {
- percentage: number;
- active: boolean;
-};
-type StatusProgressbarContainerType = {
- active: boolean;
-};
-
-export function StatusProgressbar(props: StatusProgressbarType) {
- return (
-
-
-
- );
-}
-
-const useStatus = (): { percentage: number; content: string } => {
- const datasources = useSelector(getDatasources);
+const useStatusListener = () => {
+ const datasources = useSelector(getSavedDatasources);
const pageId = useSelector(getCurrentPageId);
const actions = useSelector(getPageActions(pageId));
const widgets = useSelector(getCanvasWidgets);
@@ -137,38 +31,9 @@ const useStatus = (): { percentage: number; content: string } => {
const isFirstTimeUserOnboardingComplete = useSelector(
getFirstTimeUserOnboardingComplete,
);
- const inOnboardingWidgetSelection =
- useSelector(getInOnboardingWidgetSelection) &&
- Object.keys(widgets).length === 1;
+ const dispatch = useDispatch();
- if (isFirstTimeUserOnboardingComplete) {
- return {
- percentage: 100,
- content: createMessage(ONBOARDING_STATUS_STEPS_SIXTH),
- };
- }
-
- let content = "";
let percentage = 0;
- if (!datasources.length && !actions.length && !inOnboardingWidgetSelection) {
- content =
- Object.keys(widgets).length === 1
- ? createMessage(ONBOARDING_STATUS_STEPS_FIRST)
- : createMessage(ONBOARDING_STATUS_STEPS_FIRST_ALT);
- } else if (!actions.length && !inOnboardingWidgetSelection) {
- content = createMessage(ONBOARDING_STATUS_STEPS_SECOND);
- } else if (Object.keys(widgets).length === 1) {
- content =
- !datasources.length && !actions.length
- ? createMessage(ONBOARDING_STATUS_STEPS_THIRD_ALT)
- : createMessage(ONBOARDING_STATUS_STEPS_THIRD);
- } else if (!isConnectionPresent) {
- content = createMessage(ONBOARDING_STATUS_STEPS_FOURTH);
- } else if (!isDeployed) {
- content = createMessage(ONBOARDING_STATUS_STEPS_FIVETH);
- } else {
- content = createMessage(ONBOARDING_STATUS_STEPS_SIXTH);
- }
if (datasources.length || actions.length) {
percentage += 20;
@@ -190,72 +55,65 @@ const useStatus = (): { percentage: number; content: string } => {
percentage += 20;
}
- return {
- percentage,
- content,
- };
+ useEffect(() => {
+ dispatch(
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CONNECT_A_DATASOURCE,
+ completed: !!(datasources.length || actions.length),
+ }),
+ );
+ }, [datasources.length, actions.length]);
+
+ useEffect(() => {
+ dispatch(
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CREATE_A_QUERY,
+ completed: !!actions.length,
+ }),
+ );
+ }, [actions.length]);
+
+ useEffect(() => {
+ dispatch(
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.ADD_WIDGETS,
+ completed: Object.keys(widgets).length > 1,
+ }),
+ );
+ }, [Object.keys(widgets).length]);
+
+ useEffect(() => {
+ dispatch(
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.CONNECT_DATA_TO_WIDGET,
+ completed: isConnectionPresent,
+ }),
+ );
+ }, [isConnectionPresent]);
+
+ useEffect(() => {
+ dispatch(
+ signpostingStepUpdateInit({
+ step: SIGNPOSTING_STEP.DEPLOY_APPLICATIONS,
+ completed: isDeployed,
+ }),
+ );
+ }, [isDeployed]);
+
+ useEffect(() => {
+ if (percentage === 100 && !isFirstTimeUserOnboardingComplete) {
+ dispatch({
+ type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_COMPLETE,
+ payload: true,
+ });
+ }
+ }, [percentage, isFirstTimeUserOnboardingComplete]);
};
-export function OnboardingStatusbar(props: RouteComponentProps) {
- const dispatch = useDispatch();
- const pageId = useSelector(getCurrentPageId);
- const { content, percentage } = useStatus();
- const isChecklistPage = props.location.pathname.indexOf("/checklist") > -1;
- const isGenerateAppPage =
- props.location.pathname.indexOf("/generate-page/form") > -1;
- const isFirstTimeUserOnboardingComplete = useSelector(
- getFirstTimeUserOnboardingComplete,
- );
- if (isGenerateAppPage) {
- return null;
- }
- const endFirstTimeUserOnboarding = (event?: SyntheticEvent) => {
- event?.stopPropagation();
- dispatch({
- type: ReduxActionTypes.END_FIRST_TIME_USER_ONBOARDING,
- });
- };
- if (percentage === 100 && !isFirstTimeUserOnboardingComplete) {
- dispatch({
- type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_COMPLETE,
- payload: true,
- });
- }
+export function OnboardingStatusbar() {
+ useStatusListener();
- return (
- {
- history.push(onboardingCheckListUrl({ pageId }));
- }}
- >
-
-
- {createMessage(ONBOARDING_STATUS_GET_STARTED)}
-
-
- {content}
- {!isChecklistPage && (
-
- )}
-
-
-
- );
+ return null;
}
-export default withRouter(OnboardingStatusbar);
+export default OnboardingStatusbar;
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.test.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.test.tsx
deleted file mode 100644
index d2346835c7..0000000000
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.test.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-const dispatch = jest.fn();
-const history = jest.fn();
-
-import { integrationEditorURL } from "RouteBuilder";
-import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
-import { INTEGRATION_TABS } from "constants/routes";
-import React from "react";
-import { Provider } from "react-redux";
-import { fireEvent, render, screen } from "test/testUtils";
-import OnboardingTasks from "./Tasks";
-import { getStore, initialState } from "./testUtils";
-import urlBuilder from "entities/URLRedirect/URLAssembly";
-
-jest.mock("react-redux", () => {
- const originalModule = jest.requireActual("react-redux");
- return {
- ...originalModule,
- useDispatch: () => dispatch,
- };
-});
-
-jest.mock("utils/history", () => {
- return {
- push: history,
- listen: jest.fn(),
- };
-});
-
-let container: any;
-
-function renderComponent(store: any) {
- render(
-
-
- ,
- container,
- );
-}
-
-describe("Tasks", () => {
- beforeEach(() => {
- container = document.createElement("div");
- document.body.appendChild(container);
- urlBuilder.updateURLParams(
- {
- applicationSlug: initialState.ui.applications.currentApplication.slug,
- applicationId: initialState.entities.pageList.applicationId,
- applicationVersion: 2,
- },
- [
- {
- pageSlug: initialState.entities.pageList.pages[0].slug,
- pageId: initialState.entities.pageList.currentPageId,
- },
- ],
- );
- });
-
- afterEach(() => {
- jest.clearAllMocks();
- });
-
- it("is rendered", async () => {
- renderComponent(getStore(0));
- const wrapper = await screen.findAllByTestId("onboarding-tasks-wrapper");
- expect(wrapper.length).not.toBe(0);
- });
-
- it("is showing `Add a datasource` task", async () => {
- const store = getStore(0);
- renderComponent(store);
- const text = await screen.findAllByTestId(
- "onboarding-tasks-datasource-text",
- );
- expect(text.length).toBe(1);
- const button = await screen.findAllByTestId(
- "onboarding-tasks-datasource-button",
- );
- expect(button.length).toBe(1);
- fireEvent.click(button[0]);
- expect(history).toHaveBeenCalledWith(
- integrationEditorURL({
- pageId: initialState.entities.pageList.currentPageId,
- selectedTab: INTEGRATION_TABS.NEW,
- }),
- );
- const alt = await screen.findAllByTestId("onboarding-tasks-datasource-alt");
- expect(alt.length).toBe(1);
- fireEvent.click(alt[0]);
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.TOGGLE_ONBOARDING_WIDGET_SELECTION,
- payload: true,
- });
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.SET_FORCE_WIDGET_PANEL_OPEN,
- payload: true,
- });
- });
-
- it("is showing `Add a Query` task", async () => {
- const store = getStore(1);
- renderComponent(store);
- const text = await screen.findAllByTestId("onboarding-tasks-action-text");
- expect(text.length).toBe(1);
- const button = await screen.findAllByTestId(
- "onboarding-tasks-action-button",
- );
- expect(button.length).toBe(1);
- fireEvent.click(button[0]);
- expect(history).toHaveBeenCalledWith(
- integrationEditorURL({
- pageId: initialState.entities.pageList.currentPageId,
- selectedTab: INTEGRATION_TABS.ACTIVE,
- }),
- );
- const alt = await screen.findAllByTestId("onboarding-tasks-action-alt");
- expect(alt.length).toBe(1);
- fireEvent.click(alt[0]);
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.TOGGLE_ONBOARDING_WIDGET_SELECTION,
- payload: true,
- });
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.SET_FORCE_WIDGET_PANEL_OPEN,
- payload: true,
- });
- });
-
- it("is showing `Add a widget` task", async () => {
- const store = getStore(2);
- renderComponent(store);
- const text = await screen.findAllByTestId("onboarding-tasks-widget-text");
- expect(text.length).toBe(1);
- const button = await screen.findAllByTestId(
- "onboarding-tasks-widget-button",
- );
- expect(button.length).toBe(1);
- fireEvent.click(button[0]);
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.TOGGLE_ONBOARDING_WIDGET_SELECTION,
- payload: true,
- });
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.SET_FORCE_WIDGET_PANEL_OPEN,
- payload: true,
- });
- const alt = await screen.findAllByTestId("onboarding-tasks-widget-alt");
- expect(alt.length).toBe(1);
- fireEvent.click(alt[0]);
- expect(dispatch).toHaveBeenCalledWith({
- type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
- payload: {
- applicationId: initialState.entities.pageList.applicationId,
- },
- });
- });
-});
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx
deleted file mode 100644
index f615e03735..0000000000
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Tasks.tsx
+++ /dev/null
@@ -1,344 +0,0 @@
-import { toggleInOnboardingWidgetSelection } from "actions/onboardingActions";
-import { forceOpenWidgetPanel } from "actions/widgetSidebarActions";
-import { Button, Link } from "design-system";
-import {
- ONBOARDING_TASK_DATASOURCE_BODY,
- ONBOARDING_TASK_DATASOURCE_HEADER,
- ONBOARDING_TASK_DATASOURCE_BUTTON,
- ONBOARDING_TASK_DATASOURCE_FOOTER_ACTION,
- ONBOARDING_TASK_DATASOURCE_FOOTER,
- ONBOARDING_TASK_QUERY_HEADER,
- ONBOARDING_TASK_QUERY_BODY,
- ONBOARDING_TASK_QUERY_BUTTON,
- ONBOARDING_TASK_QUERY_FOOTER_ACTION,
- ONBOARDING_TASK_WIDGET_HEADER,
- ONBOARDING_TASK_WIDGET_BODY,
- ONBOARDING_TASK_WIDGET_BUTTON,
- ONBOARDING_TASK_WIDGET_FOOTER_ACTION,
- ONBOARDING_TASK_FOOTER,
- createMessage,
-} from "@appsmith/constants/messages";
-import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
-import { INTEGRATION_TABS } from "constants/routes";
-import { ASSETS_CDN_URL } from "constants/ThirdPartyConstants";
-import React, { useEffect, useState } from "react";
-import { useDispatch, useSelector } from "react-redux";
-
-import {
- getCurrentApplicationId,
- getCurrentPageId,
-} from "selectors/editorSelectors";
-import {
- getCanvasWidgets,
- getDatasources,
- getPageActions,
-} from "selectors/entitiesSelector";
-import styled from "styled-components";
-import AnalyticsUtil from "utils/AnalyticsUtil";
-import history from "utils/history";
-import { integrationEditorURL } from "RouteBuilder";
-import { getAssetUrl, isAirgapped } from "@appsmith/utils/airgapHelpers";
-import AnonymousDataPopup from "./AnonymousDataPopup";
-import {
- getFirstTimeUserOnboardingComplete,
- getFirstTimeUserOnboardingModal,
- getIsFirstTimeUserOnboardingEnabled,
-} from "selectors/onboardingSelectors";
-import { getCurrentUser } from "selectors/usersSelectors";
-import {
- getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown,
- setFirstTimeUserOnboardingTelemetryCalloutVisibility,
-} from "utils/storage";
-import { ANONYMOUS_DATA_POPOP_TIMEOUT } from "./constants";
-import { DatasourceCreateEntryPoints } from "constants/Datasource";
-import IntroductionModal from "./IntroductionModal";
-
-const Wrapper = styled.div`
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: center;
- height: calc(100vh - 36px);
- margin: 0 auto;
- background-color: #fff;
-`;
-
-const CenteredContainer = styled.div`
- text-align: center;
- width: 529px;
-`;
-
-const TaskImageContainer = styled.div`
- width: 180px;
- margin: 0 auto;
-`;
-
-const TaskImage = styled.img`
- width: 100%;
-`;
-
-const TaskHeader = styled.h5`
- font-size: 20px;
- margin-top: 16px;
- margin-bottom: 16px;
-`;
-
-const TaskSubText = styled.p`
- width: 100%;
-`;
-
-const TaskButtonWrapper = styled.div`
- margin-top: 30px;
-`;
-
-const Taskfootnote = styled.p`
- margin-top: 30px;
- display: flex;
- justify-content: center;
-`;
-
-const getOnboardingDatasourceImg = () =>
- `${ASSETS_CDN_URL}/onboarding-datasource.svg`;
-const getOnboardingQueryImg = () => `${ASSETS_CDN_URL}/onboarding-query.svg`;
-const getOnboardingWidgetImg = () => `${ASSETS_CDN_URL}/onboarding-widget.svg`;
-
-export default function OnboardingTasks() {
- const [isAnonymousDataPopupOpen, setisAnonymousDataPopupOpen] =
- useState(false);
-
- const isFirstTimeUserOnboardingEnabled = useSelector(
- getIsFirstTimeUserOnboardingEnabled,
- );
- const applicationId = useSelector(getCurrentApplicationId);
- const pageId = useSelector(getCurrentPageId);
- let content;
- const datasources = useSelector(getDatasources);
- const actions = useSelector(getPageActions(pageId));
- const widgets = useSelector(getCanvasWidgets);
- const dispatch = useDispatch();
- const user = useSelector(getCurrentUser);
- const isAdmin = user?.isSuperUser || false;
- const isOnboardingCompleted = useSelector(getFirstTimeUserOnboardingComplete);
- const showModal = useSelector(getFirstTimeUserOnboardingModal);
-
- const hideAnonymousDataPopup = () => {
- setisAnonymousDataPopupOpen(false);
- setFirstTimeUserOnboardingTelemetryCalloutVisibility(true);
- };
-
- const showShowAnonymousDataPopup = async () => {
- const shouldPopupShow =
- !isAirgapped() &&
- isFirstTimeUserOnboardingEnabled &&
- isAdmin &&
- !isOnboardingCompleted;
- if (shouldPopupShow) {
- const isAnonymousDataPopupAlreadyOpen =
- await getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown();
- //true if the modal was already shown else show the modal and set to already shown, also hide the modal after 10 secs
- if (isAnonymousDataPopupAlreadyOpen) {
- setisAnonymousDataPopupOpen(false);
- } else {
- setisAnonymousDataPopupOpen(true);
- setTimeout(() => {
- hideAnonymousDataPopup();
- }, ANONYMOUS_DATA_POPOP_TIMEOUT);
- await setFirstTimeUserOnboardingTelemetryCalloutVisibility(true);
- }
- } else {
- setisAnonymousDataPopupOpen(shouldPopupShow);
- }
- };
-
- useEffect(() => {
- showShowAnonymousDataPopup();
- }, []);
-
- if (!datasources.length && !actions.length) {
- content = (
-
-
-
-
-
- {createMessage(ONBOARDING_TASK_DATASOURCE_HEADER)}
-
-
- {createMessage(ONBOARDING_TASK_DATASOURCE_BODY)}
-
-
-
-
-
- {createMessage(ONBOARDING_TASK_FOOTER)}
- {
- AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
- from: "CANVAS",
- });
- dispatch(toggleInOnboardingWidgetSelection(true));
- dispatch(forceOpenWidgetPanel(true));
- }}
- >
- {createMessage(ONBOARDING_TASK_DATASOURCE_FOOTER_ACTION)}
-
- {createMessage(ONBOARDING_TASK_DATASOURCE_FOOTER)}
-
-
- );
- } else if (!actions.length) {
- content = (
-
-
-
-
-
- {createMessage(ONBOARDING_TASK_QUERY_HEADER)}
-
- {createMessage(ONBOARDING_TASK_QUERY_BODY)}
-
-
-
-
- {createMessage(ONBOARDING_TASK_FOOTER)}
- {
- AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
- from: "CANVAS",
- });
- dispatch(toggleInOnboardingWidgetSelection(true));
- dispatch(forceOpenWidgetPanel(true));
- }}
- >
- {createMessage(ONBOARDING_TASK_QUERY_FOOTER_ACTION)}
-
-
-
- );
- } else if (Object.keys(widgets).length === 1) {
- content = (
-
-
-
-
-
- {createMessage(ONBOARDING_TASK_WIDGET_HEADER)}
-
- {createMessage(ONBOARDING_TASK_WIDGET_BODY)}
-
-
-
-
- {createMessage(ONBOARDING_TASK_FOOTER)}
- {
- AnalyticsUtil.logEvent("SIGNPOSTING_PUBLISH_CLICK", {
- from: "CANVAS",
- });
- dispatch({
- type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
- payload: {
- applicationId,
- },
- });
- }}
- >
- {createMessage(ONBOARDING_TASK_WIDGET_FOOTER_ACTION)}
-
- .
-
-
- );
- }
- return (
-
- {content}
- {isAnonymousDataPopupOpen && (
-
- )}
- {!isAdmin && showModal && (
- {
- dispatch({
- type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
- payload: false,
- });
- }}
- />
- )}
-
- );
-}
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/TooltipContent.tsx b/app/client/src/pages/Editor/FirstTimeUserOnboarding/TooltipContent.tsx
new file mode 100644
index 0000000000..c16a1bd2b7
--- /dev/null
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/TooltipContent.tsx
@@ -0,0 +1,131 @@
+import {
+ createMessage,
+ ONBOARDING_CHECKLIST_HEADER,
+ SIGNPOSTING_TOOLTIP,
+ SIGNPOSTING_LAST_STEP_TOOLTIP,
+ SIGNPOSTING_SUCCESS_POPUP,
+} from "@appsmith/constants/messages";
+import React, { useEffect } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { getEvaluationInverseDependencyMap } from "selectors/dataTreeSelectors";
+import {
+ getCurrentPageId,
+ getApplicationLastDeployedAt,
+} from "selectors/editorSelectors";
+import {
+ getPageActions,
+ getCanvasWidgets,
+ getSavedDatasources,
+} from "selectors/entitiesSelector";
+import { useIsWidgetActionConnectionPresent } from "../utils";
+import { showSignpostingTooltip } from "actions/onboardingActions";
+import { SIGNPOSTING_STEP } from "./Utils";
+
+const SIGNPOSTING_STEPS = [
+ SIGNPOSTING_STEP.CONNECT_A_DATASOURCE,
+ SIGNPOSTING_STEP.CREATE_A_QUERY,
+ SIGNPOSTING_STEP.ADD_WIDGETS,
+ SIGNPOSTING_STEP.CONNECT_DATA_TO_WIDGET,
+ SIGNPOSTING_STEP.DEPLOY_APPLICATIONS,
+];
+
+function TooltipContent(props: { showSignpostingTooltip: boolean }) {
+ const datasources = useSelector(getSavedDatasources);
+ const pageId = useSelector(getCurrentPageId);
+ const actions = useSelector(getPageActions(pageId));
+ const widgets = useSelector(getCanvasWidgets);
+ const deps = useSelector(getEvaluationInverseDependencyMap);
+ const isConnectionPresent = useIsWidgetActionConnectionPresent(
+ widgets,
+ actions,
+ deps,
+ );
+ const isDeployed = !!useSelector(getApplicationLastDeployedAt);
+ const dispatch = useDispatch();
+
+ let title = createMessage(ONBOARDING_CHECKLIST_HEADER);
+ let content = createMessage(SIGNPOSTING_TOOLTIP.DEFAULT.content);
+ const lastStepContent = createMessage(SIGNPOSTING_LAST_STEP_TOOLTIP);
+ let completedTasks = 0;
+
+ useEffect(() => {
+ const handleClickOutside = () => {
+ dispatch(showSignpostingTooltip(false));
+ };
+
+ document.addEventListener("click", handleClickOutside, true);
+ return () => {
+ document.removeEventListener("click", handleClickOutside, true);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (!props.showSignpostingTooltip) return;
+
+ const timer = setTimeout(() => {
+ // After a step is completed we want to show the tooltip for 8 seconds and then hide it.
+ dispatch(showSignpostingTooltip(false));
+ }, 8000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [props.showSignpostingTooltip]);
+
+ if (datasources.length) {
+ completedTasks++;
+ }
+ if (actions.length) {
+ completedTasks++;
+ }
+ if (Object.keys(widgets).length > 1) {
+ completedTasks++;
+ }
+ if (isConnectionPresent) {
+ completedTasks++;
+ }
+ if (isDeployed) {
+ completedTasks++;
+ }
+
+ if (completedTasks > 0) {
+ title = `🎉${completedTasks}/5 Steps completed`;
+ }
+
+ if (!datasources.length && !actions.length) {
+ content = createMessage(SIGNPOSTING_TOOLTIP.CONNECT_A_DATASOURCE.content);
+ } else if (!actions.length && datasources.length) {
+ content = createMessage(SIGNPOSTING_TOOLTIP.CREATE_QUERY.content);
+ } else if (Object.keys(widgets).length === 1 && actions.length) {
+ content = createMessage(SIGNPOSTING_TOOLTIP.ADD_WIDGET.content);
+ } else if (!isConnectionPresent) {
+ content = createMessage(SIGNPOSTING_TOOLTIP.CONNECT_DATA_TO_WIDGET.content);
+ } else if (!isDeployed) {
+ content = createMessage(SIGNPOSTING_TOOLTIP.DEPLOY_APPLICATION.content);
+ }
+
+ if (completedTasks === 0) {
+ title = createMessage(ONBOARDING_CHECKLIST_HEADER);
+ content = createMessage(SIGNPOSTING_TOOLTIP.DEFAULT.content);
+ }
+
+ if (completedTasks === SIGNPOSTING_STEPS.length)
+ return <>{createMessage(SIGNPOSTING_SUCCESS_POPUP.title)}>;
+
+ return (
+ <>
+ {title}
+
+
+ {completedTasks === SIGNPOSTING_STEPS.length - 1 && (
+ <>
+ {lastStepContent}
+
+ >
+ )}
+ {content}
+ >
+ );
+}
+
+export default TooltipContent;
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Utils.ts b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Utils.ts
index bf6bf3f960..b9723d75bb 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/Utils.ts
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/Utils.ts
@@ -1,17 +1,18 @@
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
-import { removeFirstTimeUserOnboardingApplicationId } from "actions/onboardingActions";
import { APPLICATIONS_URL } from "constants/routes";
import type { Dispatch } from "react";
-import AnalyticsUtil from "utils/AnalyticsUtil";
import history from "utils/history";
-export const triggerWelcomeTour = (
- dispatch: Dispatch,
- applicationId: string,
-) => {
- AnalyticsUtil.logEvent("SIGNPOSTING_WELCOME_TOUR_CLICK");
+export const triggerWelcomeTour = (dispatch: Dispatch) => {
history.push(APPLICATIONS_URL);
- dispatch(removeFirstTimeUserOnboardingApplicationId(applicationId));
dispatch({
type: ReduxActionTypes.ONBOARDING_CREATE_APPLICATION,
});
};
+
+export enum SIGNPOSTING_STEP {
+ CONNECT_A_DATASOURCE = "CONNECT_A_DATASOURCE",
+ CREATE_A_QUERY = "CREATE_A_QUERY",
+ ADD_WIDGETS = "ADD_WIDGETS",
+ CONNECT_DATA_TO_WIDGET = "CONNECT_DATA_TO_WIDGET",
+ DEPLOY_APPLICATIONS = "DEPLOY_APPLICATIONS",
+}
diff --git a/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts b/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts
index f0de37c5d9..7fb72ac448 100644
--- a/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts
+++ b/app/client/src/pages/Editor/FirstTimeUserOnboarding/testUtils.ts
@@ -38,6 +38,7 @@ export const initialState: any = {
firstTimeUserOnboardingComplete: false,
showFirstTimeUserOnboardingModal: true,
firstTimeUserOnboardingApplicationIds: ["1"],
+ stepState: [],
},
theme: {
theme: {
diff --git a/app/client/src/pages/Editor/GeneratePage/components/GeneratePageForm/GeneratePageForm.tsx b/app/client/src/pages/Editor/GeneratePage/components/GeneratePageForm/GeneratePageForm.tsx
index e09a5bd1e4..8d5b8f44e4 100644
--- a/app/client/src/pages/Editor/GeneratePage/components/GeneratePageForm/GeneratePageForm.tsx
+++ b/app/client/src/pages/Editor/GeneratePage/components/GeneratePageForm/GeneratePageForm.tsx
@@ -54,16 +54,10 @@ import {
} from "../constants";
import { Bold, Label, SelectWrapper } from "./styles";
import type { GeneratePagePayload } from "./types";
-import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
import { getCurrentApplicationId } from "selectors/editorSelectors";
-import {
- getFirstTimeUserOnboardingComplete,
- getIsFirstTimeUserOnboardingEnabled,
-} from "selectors/onboardingSelectors";
import { datasourcesEditorIdURL, integrationEditorURL } from "RouteBuilder";
import { PluginPackageName } from "entities/Action";
-import { removeFirstTimeUserOnboardingApplicationId } from "actions/onboardingActions";
import { getCurrentAppWorkspace } from "@appsmith/selectors/workspaceSelectors";
import { hasCreateDatasourcePermission } from "@appsmith/utils/permissionHelpers";
import { getPluginImages } from "selectors/entitiesSelector";
@@ -293,13 +287,6 @@ function GeneratePageForm() {
const { bucketList, failedFetchingBucketList, isFetchingBucketList } =
useS3BucketList();
- const isFirstTimeUserOnboardingEnabled = useSelector(
- getIsFirstTimeUserOnboardingEnabled,
- );
- const isFirstTimeUserOnboardingComplete = useSelector(
- getFirstTimeUserOnboardingComplete,
- );
-
const onSelectDataSource = useCallback(
(
datasource: string | undefined,
@@ -568,15 +555,6 @@ function GeneratePageForm() {
AnalyticsUtil.logEvent("GEN_CRUD_PAGE_FORM_SUBMIT");
dispatch(generateTemplateToUpdatePage(payload));
- if (isFirstTimeUserOnboardingEnabled) {
- dispatch(removeFirstTimeUserOnboardingApplicationId(applicationId));
- }
- if (isFirstTimeUserOnboardingComplete) {
- dispatch({
- type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_COMPLETE,
- payload: false,
- });
- }
};
const handleFormSubmit = () => {
diff --git a/app/client/src/pages/Editor/HelpButton.tsx b/app/client/src/pages/Editor/HelpButton.tsx
index f19401fd4a..2700500469 100644
--- a/app/client/src/pages/Editor/HelpButton.tsx
+++ b/app/client/src/pages/Editor/HelpButton.tsx
@@ -25,6 +25,19 @@ import {
import { getAppsmithConfigs } from "@appsmith/configs";
import moment from "moment/moment";
import styled from "styled-components";
+import {
+ getFirstTimeUserOnboardingModal,
+ getIsFirstTimeUserOnboardingEnabled,
+ getSignpostingSetOverlay,
+ getSignpostingTooltipVisible,
+ getSignpostingUnreadSteps,
+ inGuidedTour,
+} from "selectors/onboardingSelectors";
+import SignpostingPopup from "pages/Editor/FirstTimeUserOnboarding/Modal";
+import { showSignpostingModal } from "actions/onboardingActions";
+import { triggerWelcomeTour } from "./FirstTimeUserOnboarding/Utils";
+import { isAirgapped } from "@appsmith/utils/airgapHelpers";
+import TooltipContent from "./FirstTimeUserOnboarding/TooltipContent";
import { getInstanceId } from "@appsmith/selectors/tenantSelectors";
import { updateIntercomConsent, updateUserDetails } from "actions/userActions";
@@ -36,6 +49,15 @@ const HelpFooter = styled.div`
justify-content: space-between;
font-size: 8px;
`;
+const UnreadSteps = styled.div`
+ position: absolute;
+ height: 6px;
+ width: 6px;
+ border-radius: 50%;
+ top: 10px;
+ left: 22px;
+ background-color: var(--ads-v2-color-fg-error);
+`;
const ConsentContainer = styled.div`
padding: 10px;
`;
@@ -45,6 +67,7 @@ const ActionsRow = styled.div`
align-items: center;
margin-bottom: 8px;
`;
+
type HelpItem = {
label: string;
link?: string;
@@ -54,7 +77,7 @@ type HelpItem = {
const HELP_MENU_ITEMS: HelpItem[] = [
{
- icon: "file-line",
+ icon: "book-line",
label: "Documentation",
link: "https://docs.appsmith.com/",
},
@@ -63,11 +86,6 @@ const HELP_MENU_ITEMS: HelpItem[] = [
label: "Report a bug",
link: "https://github.com/appsmithorg/appsmith/issues/new/choose",
},
- {
- icon: "discord",
- label: "Join our discord",
- link: "https://discord.gg/rBTTVJp",
- },
];
if (intercomAppID && window.Intercom) {
@@ -78,7 +96,7 @@ if (intercomAppID && window.Intercom) {
});
}
-function IntercomConsent({
+export function IntercomConsent({
showIntercomConsent,
}: {
showIntercomConsent: (val: boolean) => void;
@@ -121,9 +139,48 @@ function IntercomConsent({
);
}
+function HelpButtonTooltip(props: {
+ isFirstTimeUserOnboardingEnabled: boolean;
+ showSignpostingTooltip: boolean;
+}) {
+ if (props.isFirstTimeUserOnboardingEnabled) {
+ return (
+
+ );
+ }
+
+ return <>{createMessage(HELP_RESOURCE_TOOLTIP)}>;
+}
+
function HelpButton() {
- const user = useSelector(getCurrentUser);
const [showIntercomConsent, setShowIntercomConsent] = useState(false);
+ const [showTooltip, setShowTooltip] = useState(false);
+ const user = useSelector(getCurrentUser);
+ const dispatch = useDispatch();
+ const isFirstTimeUserOnboardingEnabled = useSelector(
+ getIsFirstTimeUserOnboardingEnabled,
+ );
+ const guidedTourEnabled = useSelector(inGuidedTour);
+ const showSignpostingTooltip = useSelector(getSignpostingTooltipVisible);
+ const onboardingModalOpen = useSelector(getFirstTimeUserOnboardingModal);
+ const unreadSteps = useSelector(getSignpostingUnreadSteps);
+ const setOverlay = useSelector(getSignpostingSetOverlay);
+ const isAirgappedInstance = isAirgapped();
+ const showUnreadSteps =
+ !!unreadSteps.length &&
+ isFirstTimeUserOnboardingEnabled &&
+ !onboardingModalOpen;
+ const menuProps = isFirstTimeUserOnboardingEnabled
+ ? {
+ open: onboardingModalOpen,
+ }
+ : {};
+ const tooltipProps = isFirstTimeUserOnboardingEnabled
+ ? {
+ visible: showTooltip || showSignpostingTooltip,
+ onVisibleChange: setShowTooltip,
+ }
+ : {};
useEffect(() => {
bootIntercom(user);
@@ -133,16 +190,38 @@ function HelpButton() {