feat: signposting update (#24389)
This commit is contained in:
parent
6df0810cc9
commit
d9155b67e5
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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']"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2151,5 +2151,4 @@ Cypress.Commands.add("SelectFromMultiSelect", (options) => {
|
|||
|
||||
Cypress.Commands.add("skipSignposting", () => {
|
||||
onboarding.closeIntroModal();
|
||||
onboarding.skipSignposting();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="absolute top-5">
|
||||
<Wrapper className="z-[1] self-center">
|
||||
<Callout
|
||||
isClosable
|
||||
kind="info"
|
||||
|
|
@ -42,12 +112,10 @@ export default function AnonymousDataPopup(props: {
|
|||
onClick: () => handleLinkClick(TELEMETRY_DOCS_PAGE_URL),
|
||||
},
|
||||
]}
|
||||
onClose={() => {
|
||||
props.onCloseCallout();
|
||||
}}
|
||||
onClose={hideAnonymousDataPopup}
|
||||
>
|
||||
{createMessage(ONBOARDING_TELEMETRY_POPUP)}
|
||||
</Callout>
|
||||
</div>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<Provider store={store}>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<HTMLDivElement>(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 (
|
||||
<div className="flex pt-0.5 flex-1 flex-col">
|
||||
<ListItem
|
||||
className={classNames({
|
||||
"flex items-center justify-between": true,
|
||||
})}
|
||||
completed={props.completed}
|
||||
data-testid={props.testid}
|
||||
disabled={props.disabled}
|
||||
onClick={
|
||||
props.completed
|
||||
? () => null
|
||||
: () => {
|
||||
props.onClick();
|
||||
}
|
||||
}
|
||||
>
|
||||
<Sibling
|
||||
className="flex flex-1 items-center gap-2.5"
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{props.completed ? (
|
||||
<LottieAnimationWrapper>
|
||||
<LottieAnimationContainer ref={tickMarkRef} />
|
||||
</LottieAnimationWrapper>
|
||||
) : (
|
||||
<PrefixCircle disabled={props.disabled} />
|
||||
)}
|
||||
<div>
|
||||
<Text
|
||||
className={classNames({
|
||||
"signposting-strikethrough-bold":
|
||||
props.completed && !stepState?.read,
|
||||
"signposting-strikethrough-static":
|
||||
props.completed && stepState?.read,
|
||||
"signposting-strikethrough": true,
|
||||
})}
|
||||
color={
|
||||
!props.disabled
|
||||
? "var(--ads-v2-color-bg-brand-secondary)"
|
||||
: "var(--ads-v2-color-fg-subtle)"
|
||||
}
|
||||
kind="heading-xs"
|
||||
>
|
||||
{props.boldText}
|
||||
{props.normalPrefixText && (
|
||||
<Text
|
||||
color={!props.disabled ? "" : "var(--ads-v2-color-fg-subtle)"}
|
||||
>
|
||||
{props.normalPrefixText}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
<br />
|
||||
<Text
|
||||
className={classNames({
|
||||
"signposting-strikethrough-normal":
|
||||
props.completed && !stepState?.read,
|
||||
"signposting-strikethrough-static":
|
||||
props.completed && stepState?.read,
|
||||
"signposting-strikethrough": true,
|
||||
})}
|
||||
color={!props.disabled ? "" : "var(--ads-v2-color-fg-subtle)"}
|
||||
>
|
||||
{props.normalText}
|
||||
</Text>
|
||||
</div>
|
||||
</Sibling>
|
||||
<Tooltip
|
||||
align={{
|
||||
targetOffset: [13, 0],
|
||||
}}
|
||||
content={createMessage(SIGNPOSTING_TOOLTIP.DOCUMENTATION.content)}
|
||||
placement={"bottomLeft"}
|
||||
>
|
||||
<div className="absolute right-3">
|
||||
<Button
|
||||
isDisabled={props.disabled}
|
||||
isIconButton
|
||||
kind="tertiary"
|
||||
onClick={(e) => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_INFO_CLICK", {
|
||||
step: props.step,
|
||||
});
|
||||
window.open(
|
||||
props.docLink ?? "https://docs.appsmith.com/",
|
||||
"_blank",
|
||||
);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
startIcon="book-line"
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
<StyledDivider className="mt-0.5" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <Redirect to={builderURL({ pageId })} />;
|
||||
}
|
||||
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 (
|
||||
<Wrapper data-testid="checklist-wrapper">
|
||||
<Link
|
||||
className="t--checklist-back"
|
||||
onClick={() => history.push(builderURL({ pageId }))}
|
||||
startIcon="back-control"
|
||||
>
|
||||
Back
|
||||
</Link>
|
||||
{isCompleted && (
|
||||
<Banner data-testid="checklist-completion-banner">
|
||||
<BannerHeader>
|
||||
{createMessage(ONBOARDING_CHECKLIST_BANNER_HEADER)}
|
||||
</BannerHeader>
|
||||
<BannerText>
|
||||
{createMessage(ONBOARDING_CHECKLIST_BANNER_BODY)}
|
||||
</BannerText>
|
||||
<Button onClick={() => history.push(APPLICATIONS_URL)} size="md">
|
||||
{createMessage(ONBOARDING_CHECKLIST_BANNER_BUTTON)}
|
||||
</Button>
|
||||
</Banner>
|
||||
)}
|
||||
<Pageheader className="font-bold py-6">
|
||||
{createMessage(ONBOARDING_CHECKLIST_HEADER)}
|
||||
</Pageheader>
|
||||
<PageSubHeader>{createMessage(ONBOARDING_CHECKLIST_BODY)}</PageSubHeader>
|
||||
<StatusWrapper>
|
||||
<span
|
||||
className="t--checklist-complete-status"
|
||||
data-testid="checklist-completion-info"
|
||||
|
||||
useEffect(() => {
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
className="flex justify-between pb-3 items-center"
|
||||
data-testid="checklist-completion-banner"
|
||||
>
|
||||
{completedTasks} of 5
|
||||
</span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_COMPLETE_TEXT)}
|
||||
</StatusWrapper>
|
||||
<StyledList>
|
||||
<StyledListItem>
|
||||
<StyledListItemTextWrapper>
|
||||
<StyledCompleteMarker>
|
||||
<Icon
|
||||
className="flex"
|
||||
color={
|
||||
datasources.length || actions.length
|
||||
? "var(--ads-v2-color-fg-success)"
|
||||
: ""
|
||||
}
|
||||
data-testid="checklist-datasource-complete-icon"
|
||||
name="oval-check"
|
||||
size="lg"
|
||||
/>
|
||||
</StyledCompleteMarker>
|
||||
<ChecklistText active={!!datasources.length || !!actions.length}>
|
||||
<span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.bold)}
|
||||
</span>
|
||||
|
||||
{createMessage(ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.normal)}
|
||||
</ChecklistText>
|
||||
</StyledListItemTextWrapper>
|
||||
{!datasources.length && !actions.length && (
|
||||
<Button
|
||||
className="t--checklist-datasource-button"
|
||||
data-testid="checklist-datasource-button"
|
||||
kind={
|
||||
suggestedNextAction ===
|
||||
createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.CONNECT_A_DATASOURCE,
|
||||
)
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_CREATE_DATASOURCE_CLICK", {
|
||||
from: "CHECKLIST",
|
||||
});
|
||||
history.push(
|
||||
integrationEditorURL({
|
||||
pageId,
|
||||
selectedTab: INTEGRATION_TABS.NEW,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.CONNECT_A_DATASOURCE,
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</StyledListItem>
|
||||
<StyledListItem>
|
||||
<StyledListItemTextWrapper>
|
||||
<StyledCompleteMarker>
|
||||
<Icon
|
||||
className="flex"
|
||||
color={actions.length ? "var(--ads-v2-color-fg-success)" : ""}
|
||||
data-testid="checklist-action-complete-icon"
|
||||
name="oval-check"
|
||||
size="lg"
|
||||
/>
|
||||
</StyledCompleteMarker>
|
||||
<ChecklistText active={!!actions.length}>
|
||||
<span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.bold)}
|
||||
</span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.normal)}
|
||||
</ChecklistText>
|
||||
</StyledListItemTextWrapper>
|
||||
{!actions.length && (
|
||||
<Button
|
||||
className="t--checklist-action-button"
|
||||
data-testid="checklist-action-button"
|
||||
isDisabled={!datasources.length}
|
||||
kind={
|
||||
suggestedNextAction ===
|
||||
createMessage(() => ONBOARDING_CHECKLIST_ACTIONS.CREATE_A_QUERY)
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_CREATE_QUERY_CLICK", {
|
||||
from: "CHECKLIST",
|
||||
});
|
||||
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,
|
||||
},
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(() => ONBOARDING_CHECKLIST_ACTIONS.CREATE_A_QUERY)}
|
||||
</Button>
|
||||
)}
|
||||
</StyledListItem>
|
||||
<StyledListItem>
|
||||
<StyledListItemTextWrapper>
|
||||
<StyledCompleteMarker>
|
||||
<Icon
|
||||
className="flex"
|
||||
color={
|
||||
Object.keys(widgets).length > 1
|
||||
? "var(--ads-v2-color-fg-success)"
|
||||
: ""
|
||||
}
|
||||
data-testid="checklist-widget-complete-icon"
|
||||
name="oval-check"
|
||||
size="lg"
|
||||
/>
|
||||
</StyledCompleteMarker>
|
||||
<ChecklistText active={Object.keys(widgets).length > 1}>
|
||||
<span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.bold)}
|
||||
</span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.normal)}
|
||||
</ChecklistText>
|
||||
</StyledListItemTextWrapper>
|
||||
{Object.keys(widgets).length === 1 && (
|
||||
<Button
|
||||
className="t--checklist-widget-button"
|
||||
data-testid="checklist-widget-button"
|
||||
kind={
|
||||
suggestedNextAction ===
|
||||
createMessage(() => ONBOARDING_CHECKLIST_ACTIONS.ADD_WIDGETS)
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
|
||||
from: "CHECKLIST",
|
||||
});
|
||||
dispatch(toggleInOnboardingWidgetSelection(true));
|
||||
dispatch(forceOpenWidgetPanel(true));
|
||||
history.push(builderURL({ pageId }));
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(() => ONBOARDING_CHECKLIST_ACTIONS.ADD_WIDGETS)}
|
||||
</Button>
|
||||
)}
|
||||
</StyledListItem>
|
||||
<StyledListItem>
|
||||
<StyledListItemTextWrapper>
|
||||
<StyledCompleteMarker>
|
||||
<Icon
|
||||
className="flex"
|
||||
color={
|
||||
isConnectionPresent ? "var(--ads-v2-color-fg-success)" : ""
|
||||
}
|
||||
data-testid="checklist-connection-complete-icon"
|
||||
name="oval-check"
|
||||
size="lg"
|
||||
/>
|
||||
</StyledCompleteMarker>
|
||||
<ChecklistText active={!!isConnectionPresent}>
|
||||
<span>
|
||||
{createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.bold,
|
||||
)}
|
||||
</span>
|
||||
|
||||
{createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.normal,
|
||||
)}
|
||||
</ChecklistText>
|
||||
</StyledListItemTextWrapper>
|
||||
{!isConnectionPresent && (
|
||||
<Button
|
||||
className="t--checklist-connection-button"
|
||||
data-testid="checklist-connection-button"
|
||||
isDisabled={Object.keys(widgets).length === 1 || !actions.length}
|
||||
kind={
|
||||
suggestedNextAction ===
|
||||
createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.CONNECT_DATA_TO_WIDGET,
|
||||
)
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
onClick={onconnectYourWidget}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.CONNECT_DATA_TO_WIDGET,
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</StyledListItem>
|
||||
<StyledListItem>
|
||||
<StyledListItemTextWrapper>
|
||||
<StyledCompleteMarker>
|
||||
<Icon
|
||||
className="flex"
|
||||
color={isDeployed ? "var(--ads-v2-color-fg-success)" : ""}
|
||||
data-testid="checklist-deploy-complete-icon"
|
||||
name="oval-check"
|
||||
size="lg"
|
||||
/>
|
||||
</StyledCompleteMarker>
|
||||
<ChecklistText active={!!isDeployed}>
|
||||
<span>
|
||||
{createMessage(ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.bold)}
|
||||
</span>
|
||||
|
||||
{createMessage(ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.normal)}
|
||||
</ChecklistText>
|
||||
</StyledListItemTextWrapper>
|
||||
{!isDeployed && (
|
||||
<Button
|
||||
className="t--checklist-deploy-button"
|
||||
data-testid="checklist-deploy-button"
|
||||
kind={
|
||||
suggestedNextAction ===
|
||||
createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.DEPLOY_APPLICATIONS,
|
||||
)
|
||||
? "primary"
|
||||
: "secondary"
|
||||
}
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_PUBLISH_CLICK", {
|
||||
from: "CHECKLIST",
|
||||
});
|
||||
dispatch({
|
||||
type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
|
||||
payload: {
|
||||
applicationId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(
|
||||
() => ONBOARDING_CHECKLIST_ACTIONS.DEPLOY_APPLICATIONS,
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</StyledListItem>
|
||||
</StyledList>
|
||||
{!isAirgappedInstance && (
|
||||
<StyledFooter
|
||||
className="flex"
|
||||
onClick={() => triggerWelcomeTour(dispatch, applicationId)}
|
||||
>
|
||||
<StyledCompleteMarker>
|
||||
<Icon name="rocket" size="lg" />
|
||||
</StyledCompleteMarker>
|
||||
<Text style={{ lineHeight: "14px" }} type={TextType.P1}>
|
||||
{createMessage(ONBOARDING_CHECKLIST_FOOTER)}
|
||||
<Text
|
||||
className="flex-1"
|
||||
color="var(--ads-v2-color-fg-emphasis)"
|
||||
kind="heading-m"
|
||||
>
|
||||
{createMessage(SIGNPOSTING_SUCCESS_POPUP.title)}
|
||||
</Text>
|
||||
<Icon name="arrow-forward" size="md" />
|
||||
</StyledFooter>
|
||||
)}
|
||||
</Wrapper>
|
||||
<Button
|
||||
isIconButton
|
||||
kind="tertiary"
|
||||
onClick={() => {
|
||||
dispatch(showSignpostingModal(false));
|
||||
}}
|
||||
startIcon={"close-line"}
|
||||
/>
|
||||
</div>
|
||||
<Text color="var(--ads-v2-color-bg-brand-secondary)" kind="heading-xs">
|
||||
{createMessage(SIGNPOSTING_SUCCESS_POPUP.subtitle)}
|
||||
</Text>
|
||||
<StyledDivider className="mt-4" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex-1">
|
||||
<div className="flex justify-between pb-3 items-center">
|
||||
<Text color="var(--ads-v2-color-fg-emphasis)" kind="heading-m">
|
||||
{createMessage(ONBOARDING_CHECKLIST_HEADER)}
|
||||
</Text>
|
||||
<Button
|
||||
data-testid="signposting-modal-close-btn"
|
||||
isIconButton
|
||||
kind="tertiary"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_MODAL_CLOSE_CLICK");
|
||||
dispatch(showSignpostingModal(false));
|
||||
}}
|
||||
startIcon={"close-line"}
|
||||
/>
|
||||
</div>
|
||||
<Text color="var(--ads-v2-color-bg-brand-secondary)" kind="heading-xs">
|
||||
{createMessage(SIGNPOSTING_POPUP_SUBTITLE)}
|
||||
</Text>
|
||||
<div className="mt-5">
|
||||
<Text
|
||||
color="var(--ads-v2-color-bg-brand-secondary)"
|
||||
data-testid="checklist-completion-info"
|
||||
kind="heading-xs"
|
||||
>
|
||||
{completedTasks} of 5{" "}
|
||||
</Text>
|
||||
<Text>complete</Text>
|
||||
</div>
|
||||
<StyledDivider className="mt-1" />
|
||||
</div>
|
||||
<div
|
||||
className="overflow-auto min-h-[60px]"
|
||||
data-testid="checklist-wrapper"
|
||||
>
|
||||
<CheckListItem
|
||||
boldText={createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.bold,
|
||||
)}
|
||||
completed={!!(datasources.length || actions.length)}
|
||||
disabled={false}
|
||||
docLink="https://docs.appsmith.com/core-concepts/connecting-to-data-sources"
|
||||
normalText={createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_SOURCE.normal,
|
||||
)}
|
||||
onClick={() => {
|
||||
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"}
|
||||
/>
|
||||
<CheckListItem
|
||||
boldText={createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.bold)}
|
||||
completed={!!actions.length}
|
||||
disabled={!datasources.length && !actions.length}
|
||||
docLink="https://docs.appsmith.com/core-concepts/data-access-and-binding/querying-a-database"
|
||||
normalPrefixText={createMessage(
|
||||
ONBOARDING_CHECKLIST_CREATE_A_QUERY.normalPrefix,
|
||||
)}
|
||||
normalText={createMessage(ONBOARDING_CHECKLIST_CREATE_A_QUERY.normal)}
|
||||
onClick={() => {
|
||||
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"}
|
||||
/>
|
||||
<CheckListItem
|
||||
boldText={createMessage(ONBOARDING_CHECKLIST_ADD_WIDGETS.bold)}
|
||||
completed={Object.keys(widgets).length > 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"}
|
||||
/>
|
||||
<CheckListItem
|
||||
boldText={createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.bold,
|
||||
)}
|
||||
completed={isConnectionPresent}
|
||||
disabled={Object.keys(widgets).length === 1 || !actions.length}
|
||||
docLink="https://docs.appsmith.com/core-concepts/data-access-and-binding/displaying-data-read"
|
||||
normalText={createMessage(
|
||||
ONBOARDING_CHECKLIST_CONNECT_DATA_TO_WIDGET.normal,
|
||||
)}
|
||||
onClick={onconnectYourWidget}
|
||||
step={SIGNPOSTING_STEP.CONNECT_DATA_TO_WIDGET}
|
||||
testid={"checklist-connection"}
|
||||
/>
|
||||
<CheckListItem
|
||||
boldText={createMessage(
|
||||
ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.bold,
|
||||
)}
|
||||
completed={isDeployed}
|
||||
disabled={false}
|
||||
normalText={createMessage(
|
||||
ONBOARDING_CHECKLIST_DEPLOY_APPLICATIONS.normal,
|
||||
)}
|
||||
onClick={() => {
|
||||
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"}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
140
app/client/src/pages/Editor/FirstTimeUserOnboarding/HelpMenu.tsx
Normal file
140
app/client/src/pages/Editor/FirstTimeUserOnboarding/HelpMenu.tsx
Normal file
|
|
@ -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 (
|
||||
<div
|
||||
className={classNames({
|
||||
"mt-3.5": !props.showIntercomConsent,
|
||||
"flex-1": true,
|
||||
})}
|
||||
>
|
||||
{props.showIntercomConsent ? (
|
||||
<IntercomConsent showIntercomConsent={props.setShowIntercomConsent} />
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
color="var(--ads-v2-color-bg-brand-secondary)"
|
||||
kind="heading-xs"
|
||||
>
|
||||
Help & Resources
|
||||
</Text>
|
||||
<div className="flex gap-2 flex-wrap mt-2">
|
||||
<Button
|
||||
data-testid="editor-welcome-tour"
|
||||
kind="secondary"
|
||||
onClick={() => {
|
||||
triggerWelcomeTour(dispatch);
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_WELCOME_TOUR_CLICK");
|
||||
}}
|
||||
startIcon={"guide"}
|
||||
>
|
||||
Try guided tour
|
||||
</Button>
|
||||
{HELP_MENU_ITEMS.map((item) => {
|
||||
return (
|
||||
<Button
|
||||
key={item.label}
|
||||
kind="secondary"
|
||||
onClick={(e: any) => {
|
||||
if (item.link) {
|
||||
window.open(item.link, "_blank");
|
||||
}
|
||||
if (item.id === "intercom-trigger") {
|
||||
e?.preventDefault();
|
||||
if (intercomAppID && window.Intercom) {
|
||||
if (user?.isIntercomConsentGiven || cloudHosting) {
|
||||
window.Intercom("show");
|
||||
} else {
|
||||
props.setShowIntercomConsent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
startIcon={item.icon}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{appVersion.id && (
|
||||
<HelpFooter className="mt-2">
|
||||
<StyledText color="var(--ads-v2-color-fg-muted)" kind={"action-s"}>
|
||||
{createMessage(
|
||||
APPSMITH_DISPLAY_VERSION,
|
||||
appVersion.edition,
|
||||
appVersion.id,
|
||||
cloudHosting,
|
||||
)}
|
||||
</StyledText>
|
||||
<StyledText color="var(--ads-v2-color-fg-muted)" kind={"action-s"}>
|
||||
Released {moment(appVersion.releaseDate).fromNow()}
|
||||
</StyledText>
|
||||
</HelpFooter>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HelpMenu;
|
||||
|
|
@ -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 (
|
||||
<Modal onOpenChange={closeModal} open={modalAlwaysOpen}>
|
||||
<ModalContent
|
||||
onEscapeKeyDown={(e) => e.preventDefault()}
|
||||
onInteractOutside={(e) => e.preventDefault()}
|
||||
style={{ width: "920px" }}
|
||||
>
|
||||
<ModalHeader className="t--how-appsmith-works-modal-header">
|
||||
{createMessage(WELCOME_TO_APPSMITH)}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<ModalSubHeader>{createMessage(HOW_APPSMITH_WORKS)}</ModalSubHeader>
|
||||
<ModalContentWrapper>
|
||||
<ModalContentRow border>
|
||||
<ModalContentTextWrapper>
|
||||
<div>
|
||||
<StyledCount>1</StyledCount>
|
||||
</div>
|
||||
<ModalContentItem>
|
||||
<ModalContentHeader>
|
||||
{createMessage(ONBOARDING_INTRO_CONNECT_YOUR_DATABASE)}
|
||||
</ModalContentHeader>
|
||||
<ModalContentDescription>
|
||||
{createMessage(QUERY_YOUR_DATABASE)}
|
||||
</ModalContentDescription>
|
||||
</ModalContentItem>
|
||||
</ModalContentTextWrapper>
|
||||
<StyledImgWrapper>
|
||||
<StyledImg
|
||||
alt="connect-data-image"
|
||||
src={getAssetUrl(getConnectDataImg())}
|
||||
/>
|
||||
</StyledImgWrapper>
|
||||
</ModalContentRow>
|
||||
<ModalContentRow border>
|
||||
<ModalContentTextWrapper>
|
||||
<div>
|
||||
<StyledCount>2</StyledCount>
|
||||
</div>
|
||||
<ModalContentItem>
|
||||
<ModalContentHeader>
|
||||
{createMessage(DRAG_AND_DROP)}
|
||||
</ModalContentHeader>
|
||||
<ModalContentDescription>
|
||||
{createMessage(CUSTOMIZE_WIDGET_STYLING)}
|
||||
</ModalContentDescription>
|
||||
</ModalContentItem>
|
||||
</ModalContentTextWrapper>
|
||||
<StyledImgWrapper>
|
||||
<StyledImg
|
||||
alt="drag-and-drop-img"
|
||||
src={getAssetUrl(getDragAndDropImg())}
|
||||
/>
|
||||
</StyledImgWrapper>
|
||||
</ModalContentRow>
|
||||
<ModalContentRow className="border-b-0">
|
||||
<ModalContentTextWrapper>
|
||||
<div>
|
||||
<StyledCount>3</StyledCount>
|
||||
</div>
|
||||
<ModalContentItem>
|
||||
<ModalContentHeader>
|
||||
{createMessage(ONBOARDING_INTRO_PUBLISH)}
|
||||
</ModalContentHeader>
|
||||
<ModalContentDescription>
|
||||
{createMessage(CHOOSE_ACCESS_CONTROL_ROLES)}
|
||||
</ModalContentDescription>
|
||||
</ModalContentItem>
|
||||
</ModalContentTextWrapper>
|
||||
<StyledImgWrapper>
|
||||
<StyledImg
|
||||
alt="publish-image"
|
||||
src={getAssetUrl(getPublishAppsImg())}
|
||||
/>
|
||||
</StyledImgWrapper>
|
||||
</ModalContentRow>
|
||||
</ModalContentWrapper>
|
||||
<ModalFooterText>
|
||||
{createMessage(ONBOARDING_INTRO_FOOTER)}
|
||||
</ModalFooterText>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{!isAirgappedInstance && (
|
||||
<Button
|
||||
className="t--introduction-modal-welcome-tour-button"
|
||||
kind="secondary"
|
||||
onClick={() => triggerWelcomeTour(dispatch, applicationId)}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(START_TUTORIAL)}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="t--introduction-modal-build-button"
|
||||
onClick={onBuildApp}
|
||||
size="md"
|
||||
>
|
||||
{createMessage(BUILD_MY_FIRST_APP)}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<StyledMenuContent
|
||||
animate={props.setOverlay}
|
||||
collisionPadding={10}
|
||||
data-testid="signposting-modal"
|
||||
onInteractOutside={() => {
|
||||
dispatch(showSignpostingModal(false));
|
||||
}}
|
||||
width={SIGNPOSTING_POPUP_WIDTH}
|
||||
>
|
||||
<Wrapper>
|
||||
{!props.showIntercomConsent && <Checklist />}
|
||||
<HelpMenu
|
||||
setShowIntercomConsent={props.setShowIntercomConsent}
|
||||
showIntercomConsent={props.showIntercomConsent}
|
||||
/>
|
||||
</Wrapper>
|
||||
</StyledMenuContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default OnboardingModal;
|
||||
|
|
@ -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 (
|
||||
<StyledOverlay
|
||||
className="fixed top-0 w-full h-full overflow-hidden"
|
||||
onClick={() => {
|
||||
dispatch(showSignpostingModal(false));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Overlay;
|
||||
|
|
@ -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(
|
||||
<Provider store={store}>
|
||||
|
|
@ -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", () => {
|
||||
|
|
|
|||
|
|
@ -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<StatusProgressbarContainerType>`
|
||||
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<StatusProgressbarType>`
|
||||
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 (
|
||||
<ProgressContainer {...props}>
|
||||
<Progressbar {...props} />
|
||||
</ProgressContainer>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Wrapper
|
||||
active={isChecklistPage}
|
||||
className="sticky top-0 t--onboarding-statusbar"
|
||||
data-testid="statusbar-container"
|
||||
onClick={() => {
|
||||
history.push(onboardingCheckListUrl({ pageId }));
|
||||
}}
|
||||
>
|
||||
<StyledClose
|
||||
className="hover-icons"
|
||||
data-testid="statusbar-skip"
|
||||
isIconButton
|
||||
kind={isChecklistPage ? "primary" : "tertiary"}
|
||||
onClick={endFirstTimeUserOnboarding}
|
||||
size="sm"
|
||||
startIcon="close-control"
|
||||
/>
|
||||
<TitleWrapper>
|
||||
{createMessage(ONBOARDING_STATUS_GET_STARTED)}
|
||||
</TitleWrapper>
|
||||
<StatusText className="mt-1">
|
||||
<span data-testid="statusbar-text">{content}</span>
|
||||
{!isChecklistPage && (
|
||||
<Icon className="hover-icons" name="right-arrow-2" size="md" />
|
||||
)}
|
||||
</StatusText>
|
||||
<StatusProgressbar
|
||||
active={isChecklistPage}
|
||||
data-testid="statusbar-text"
|
||||
percentage={percentage}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default withRouter(OnboardingStatusbar);
|
||||
export default OnboardingStatusbar;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<Provider store={store}>
|
||||
<OnboardingTasks />
|
||||
</Provider>,
|
||||
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,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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 = (
|
||||
<CenteredContainer>
|
||||
<TaskImageContainer>
|
||||
<TaskImage src={getAssetUrl(getOnboardingDatasourceImg())} />
|
||||
</TaskImageContainer>
|
||||
<TaskHeader
|
||||
className="t--tasks-datasource-header"
|
||||
data-testid="onboarding-tasks-datasource-text"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_DATASOURCE_HEADER)}
|
||||
</TaskHeader>
|
||||
<TaskSubText>
|
||||
{createMessage(ONBOARDING_TASK_DATASOURCE_BODY)}
|
||||
</TaskSubText>
|
||||
<TaskButtonWrapper>
|
||||
<Button
|
||||
className="t--tasks-datasource-button"
|
||||
data-testid="onboarding-tasks-datasource-button"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_CREATE_DATASOURCE_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
history.push(
|
||||
integrationEditorURL({
|
||||
pageId,
|
||||
selectedTab: INTEGRATION_TABS.NEW,
|
||||
}),
|
||||
);
|
||||
// Event for datasource creation click
|
||||
const entryPoint = DatasourceCreateEntryPoints.ONBOARDING;
|
||||
AnalyticsUtil.logEvent("NAVIGATE_TO_CREATE_NEW_DATASOURCE_PAGE", {
|
||||
entryPoint,
|
||||
});
|
||||
}}
|
||||
size="md"
|
||||
startIcon="plus"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_DATASOURCE_BUTTON)}
|
||||
</Button>
|
||||
</TaskButtonWrapper>
|
||||
<Taskfootnote>
|
||||
{createMessage(ONBOARDING_TASK_FOOTER)}
|
||||
<Link
|
||||
className="t--tasks-datasource-alternate-button"
|
||||
data-testid="onboarding-tasks-datasource-alt"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
dispatch(toggleInOnboardingWidgetSelection(true));
|
||||
dispatch(forceOpenWidgetPanel(true));
|
||||
}}
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_DATASOURCE_FOOTER_ACTION)}
|
||||
</Link>
|
||||
{createMessage(ONBOARDING_TASK_DATASOURCE_FOOTER)}
|
||||
</Taskfootnote>
|
||||
</CenteredContainer>
|
||||
);
|
||||
} else if (!actions.length) {
|
||||
content = (
|
||||
<CenteredContainer>
|
||||
<TaskImageContainer>
|
||||
<TaskImage src={getAssetUrl(getOnboardingQueryImg())} />
|
||||
</TaskImageContainer>
|
||||
<TaskHeader
|
||||
className="t--tasks-datasource-header"
|
||||
data-testid="onboarding-tasks-action-text"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_QUERY_HEADER)}
|
||||
</TaskHeader>
|
||||
<TaskSubText>{createMessage(ONBOARDING_TASK_QUERY_BODY)}</TaskSubText>
|
||||
<TaskButtonWrapper>
|
||||
<Button
|
||||
className="t--tasks-action-button"
|
||||
data-testid="onboarding-tasks-action-button"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_CREATE_QUERY_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
history.push(
|
||||
integrationEditorURL({
|
||||
pageId,
|
||||
selectedTab: INTEGRATION_TABS.ACTIVE,
|
||||
}),
|
||||
);
|
||||
}}
|
||||
size="md"
|
||||
startIcon="plus"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_QUERY_BUTTON)}
|
||||
</Button>
|
||||
</TaskButtonWrapper>
|
||||
<Taskfootnote>
|
||||
{createMessage(ONBOARDING_TASK_FOOTER)}
|
||||
<Link
|
||||
className="t--tasks-action-alternate-button"
|
||||
data-testid="onboarding-tasks-action-alt"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
dispatch(toggleInOnboardingWidgetSelection(true));
|
||||
dispatch(forceOpenWidgetPanel(true));
|
||||
}}
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_QUERY_FOOTER_ACTION)}
|
||||
</Link>
|
||||
</Taskfootnote>
|
||||
</CenteredContainer>
|
||||
);
|
||||
} else if (Object.keys(widgets).length === 1) {
|
||||
content = (
|
||||
<CenteredContainer>
|
||||
<TaskImageContainer>
|
||||
<TaskImage src={getAssetUrl(getOnboardingWidgetImg())} />
|
||||
</TaskImageContainer>
|
||||
<TaskHeader
|
||||
className="t--tasks-datasource-header"
|
||||
data-testid="onboarding-tasks-widget-text"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_WIDGET_HEADER)}
|
||||
</TaskHeader>
|
||||
<TaskSubText>{createMessage(ONBOARDING_TASK_WIDGET_BODY)}</TaskSubText>
|
||||
<TaskButtonWrapper>
|
||||
<Button
|
||||
className="t--tasks-widget-button"
|
||||
data-testid="onboarding-tasks-widget-button"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_ADD_WIDGET_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
dispatch(toggleInOnboardingWidgetSelection(true));
|
||||
dispatch(forceOpenWidgetPanel(true));
|
||||
}}
|
||||
size="md"
|
||||
startIcon="plus"
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_WIDGET_BUTTON)}
|
||||
</Button>
|
||||
</TaskButtonWrapper>
|
||||
<Taskfootnote>
|
||||
{createMessage(ONBOARDING_TASK_FOOTER)}
|
||||
<Link
|
||||
className="t--tasks-widget-alternate-button"
|
||||
data-testid="onboarding-tasks-widget-alt"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_PUBLISH_CLICK", {
|
||||
from: "CANVAS",
|
||||
});
|
||||
dispatch({
|
||||
type: ReduxActionTypes.PUBLISH_APPLICATION_INIT,
|
||||
payload: {
|
||||
applicationId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{createMessage(ONBOARDING_TASK_WIDGET_FOOTER_ACTION)}
|
||||
</Link>
|
||||
.
|
||||
</Taskfootnote>
|
||||
</CenteredContainer>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Wrapper data-testid="onboarding-tasks-wrapper">
|
||||
{content}
|
||||
{isAnonymousDataPopupOpen && (
|
||||
<AnonymousDataPopup onCloseCallout={hideAnonymousDataPopup} />
|
||||
)}
|
||||
{!isAdmin && showModal && (
|
||||
<IntroductionModal
|
||||
close={() => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
|
||||
payload: false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
<br />
|
||||
<br />
|
||||
{completedTasks === SIGNPOSTING_STEPS.length - 1 && (
|
||||
<>
|
||||
{lastStepContent}
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{content}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TooltipContent;
|
||||
|
|
@ -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<any>,
|
||||
applicationId: string,
|
||||
) => {
|
||||
AnalyticsUtil.logEvent("SIGNPOSTING_WELCOME_TOUR_CLICK");
|
||||
export const triggerWelcomeTour = (dispatch: Dispatch<any>) => {
|
||||
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",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export const initialState: any = {
|
|||
firstTimeUserOnboardingComplete: false,
|
||||
showFirstTimeUserOnboardingModal: true,
|
||||
firstTimeUserOnboardingApplicationIds: ["1"],
|
||||
stepState: [],
|
||||
},
|
||||
theme: {
|
||||
theme: {
|
||||
|
|
|
|||
|
|
@ -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 = () => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<TooltipContent showSignpostingTooltip={props.showSignpostingTooltip} />
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
|||
<Menu
|
||||
onOpenChange={(open) => {
|
||||
if (open) {
|
||||
if (isFirstTimeUserOnboardingEnabled) {
|
||||
dispatch(showSignpostingModal(true));
|
||||
setShowTooltip(false);
|
||||
}
|
||||
setShowIntercomConsent(false);
|
||||
AnalyticsUtil.logEvent("OPEN_HELP", { page: "Editor" });
|
||||
AnalyticsUtil.logEvent("OPEN_HELP", {
|
||||
page: "Editor",
|
||||
signpostingActive: isFirstTimeUserOnboardingEnabled,
|
||||
});
|
||||
}
|
||||
}}
|
||||
{...menuProps}
|
||||
>
|
||||
<MenuTrigger>
|
||||
<div>
|
||||
<div className="relative">
|
||||
<Tooltip
|
||||
content={createMessage(HELP_RESOURCE_TOOLTIP)}
|
||||
align={{
|
||||
targetOffset: [5, 0],
|
||||
}}
|
||||
content={
|
||||
<HelpButtonTooltip
|
||||
isFirstTimeUserOnboardingEnabled={
|
||||
isFirstTimeUserOnboardingEnabled
|
||||
}
|
||||
showSignpostingTooltip={showSignpostingTooltip}
|
||||
/>
|
||||
}
|
||||
destroyTooltipOnHide={isFirstTimeUserOnboardingEnabled}
|
||||
isDisabled={onboardingModalOpen}
|
||||
mouseLeaveDelay={0}
|
||||
placement="bottomRight"
|
||||
{...tooltipProps}
|
||||
>
|
||||
<Button
|
||||
data-testid="t--help-button"
|
||||
|
|
@ -153,56 +232,85 @@ function HelpButton() {
|
|||
Help
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{showUnreadSteps && <UnreadSteps className="unread" />}
|
||||
</div>
|
||||
</MenuTrigger>
|
||||
<MenuContent collisionPadding={10} style={{ width: HELP_MODAL_WIDTH }}>
|
||||
{showIntercomConsent ? (
|
||||
<IntercomConsent showIntercomConsent={setShowIntercomConsent} />
|
||||
) : (
|
||||
HELP_MENU_ITEMS.map((item) => (
|
||||
<MenuItem
|
||||
id={item.id}
|
||||
key={item.label}
|
||||
onSelect={(e) => {
|
||||
if (item.link) {
|
||||
window.open(item.link, "_blank");
|
||||
}
|
||||
if (item.id === "intercom-trigger") {
|
||||
e?.preventDefault();
|
||||
if (intercomAppID && window.Intercom) {
|
||||
if (user?.isIntercomConsentGiven || cloudHosting) {
|
||||
window.Intercom("show");
|
||||
} else {
|
||||
setShowIntercomConsent(true);
|
||||
{isFirstTimeUserOnboardingEnabled ? (
|
||||
<SignpostingPopup
|
||||
setOverlay={setOverlay}
|
||||
setShowIntercomConsent={setShowIntercomConsent}
|
||||
showIntercomConsent={showIntercomConsent}
|
||||
/>
|
||||
) : (
|
||||
<MenuContent collisionPadding={10} style={{ width: HELP_MODAL_WIDTH }}>
|
||||
{showIntercomConsent ? (
|
||||
<IntercomConsent showIntercomConsent={setShowIntercomConsent} />
|
||||
) : (
|
||||
<>
|
||||
{!isAirgappedInstance && !guidedTourEnabled && (
|
||||
<>
|
||||
<MenuItem
|
||||
data-testid="editor-welcome-tour"
|
||||
onSelect={() => {
|
||||
triggerWelcomeTour(dispatch);
|
||||
AnalyticsUtil.logEvent("HELP_MENU_WELCOME_TOUR_CLICK");
|
||||
}}
|
||||
startIcon="guide"
|
||||
>
|
||||
Try guided tour
|
||||
</MenuItem>
|
||||
<MenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{HELP_MENU_ITEMS.map((item) => (
|
||||
<MenuItem
|
||||
id={item.id}
|
||||
key={item.label}
|
||||
onSelect={(e) => {
|
||||
if (item.link) {
|
||||
window.open(item.link, "_blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
startIcon={item.icon}
|
||||
>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
{appVersion.id && (
|
||||
<>
|
||||
<MenuSeparator />
|
||||
<MenuItem className="menuitem-nohover">
|
||||
<HelpFooter>
|
||||
<span>
|
||||
{createMessage(
|
||||
APPSMITH_DISPLAY_VERSION,
|
||||
appVersion.edition,
|
||||
appVersion.id,
|
||||
cloudHosting,
|
||||
)}
|
||||
</span>
|
||||
<span>Released {moment(appVersion.releaseDate).fromNow()}</span>
|
||||
</HelpFooter>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</MenuContent>
|
||||
if (item.id === "intercom-trigger") {
|
||||
e?.preventDefault();
|
||||
if (intercomAppID && window.Intercom) {
|
||||
if (user?.isIntercomConsentGiven || cloudHosting) {
|
||||
window.Intercom("show");
|
||||
} else {
|
||||
setShowIntercomConsent(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
startIcon={item.icon}
|
||||
>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{appVersion.id && (
|
||||
<>
|
||||
<MenuSeparator />
|
||||
<MenuItem className="menuitem-nohover">
|
||||
<HelpFooter>
|
||||
<span>
|
||||
{createMessage(
|
||||
APPSMITH_DISPLAY_VERSION,
|
||||
appVersion.edition,
|
||||
appVersion.id,
|
||||
cloudHosting,
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
Released {moment(appVersion.releaseDate).fromNow()}
|
||||
</span>
|
||||
</HelpFooter>
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
</MenuContent>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
import Canvas from "../Canvas";
|
||||
import { CanvasResizer } from "widgets/CanvasResizer";
|
||||
import type { AppState } from "@appsmith/reducers";
|
||||
import { getIsAnonymousDataPopupVisible } from "selectors/onboardingSelectors";
|
||||
|
||||
type CanvasContainerProps = {
|
||||
isPreviewMode: boolean;
|
||||
|
|
@ -117,6 +118,7 @@ function CanvasContainer(props: CanvasContainerProps) {
|
|||
pages.length > 1;
|
||||
const isAppThemeChanging = useSelector(getAppThemeIsChanging);
|
||||
const showCanvasTopSection = useSelector(showCanvasTopSectionSelector);
|
||||
const showAnonymousDataPopup = useSelector(getIsAnonymousDataPopupVisible);
|
||||
|
||||
const isLayoutingInitialized = useDynamicAppLayout();
|
||||
const isPageInitializing = isFetchingPage || !isLayoutingInitialized;
|
||||
|
|
@ -188,12 +190,15 @@ function CanvasContainer(props: CanvasContainerProps) {
|
|||
className={classNames({
|
||||
[`${getCanvasClassName()} scrollbar-thin`]: true,
|
||||
"mt-0": shouldShowSnapShotBanner || !shouldHaveTopMargin,
|
||||
"mt-4": !shouldShowSnapShotBanner && showCanvasTopSection,
|
||||
"mt-4":
|
||||
!shouldShowSnapShotBanner &&
|
||||
(showCanvasTopSection || showAnonymousDataPopup),
|
||||
"mt-8":
|
||||
!shouldShowSnapShotBanner &&
|
||||
shouldHaveTopMargin &&
|
||||
!showCanvasTopSection &&
|
||||
!isPreviewingNavigation,
|
||||
!isPreviewingNavigation &&
|
||||
!showAnonymousDataPopup,
|
||||
"mt-24": shouldShowSnapShotBanner,
|
||||
})}
|
||||
id={"canvas-viewport"}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
|
|||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
import OnboardingTasks from "../FirstTimeUserOnboarding/Tasks";
|
||||
import CrudInfoModal from "../GeneratePage/components/CrudInfoModal";
|
||||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
import {
|
||||
|
|
@ -25,10 +24,7 @@ import {
|
|||
import { setCanvasSelectionFromEditor } from "actions/canvasSelectionActions";
|
||||
import { closePropertyPane, closeTableFilterPane } from "actions/widgetActions";
|
||||
import { useAllowEditorDragToSelect } from "utils/hooks/useAllowEditorDragToSelect";
|
||||
import {
|
||||
getIsOnboardingTasksView,
|
||||
inGuidedTour,
|
||||
} from "selectors/onboardingSelectors";
|
||||
import { inGuidedTour } from "selectors/onboardingSelectors";
|
||||
import EditorContextProvider from "components/editorComponents/EditorContextProvider";
|
||||
import Guide from "../GuidedTour/Guide";
|
||||
import CanvasContainer from "./CanvasContainer";
|
||||
|
|
@ -49,6 +45,7 @@ import { useIsMobileDevice } from "utils/hooks/useDeviceDetect";
|
|||
import classNames from "classnames";
|
||||
import { getSnapshotUpdatedTime } from "selectors/autoLayoutSelectors";
|
||||
import { getReadableSnapShotDetails } from "utils/autoLayout/AutoLayoutUtils";
|
||||
import AnonymousDataPopup from "../FirstTimeUserOnboarding/AnonymousDataPopup";
|
||||
|
||||
function WidgetsEditor() {
|
||||
const { deselectAll, focusWidget } = useWidgetSelection();
|
||||
|
|
@ -56,7 +53,6 @@ function WidgetsEditor() {
|
|||
const currentPageId = useSelector(getCurrentPageId);
|
||||
const currentPageName = useSelector(getCurrentPageName);
|
||||
const currentApp = useSelector(getCurrentApplication);
|
||||
const showOnboardingTasks = useSelector(getIsOnboardingTasksView);
|
||||
const guidedTourEnabled = useSelector(inGuidedTour);
|
||||
const isPreviewMode = useSelector(previewModeSelector);
|
||||
const lastUpdatedTime = useSelector(getSnapshotUpdatedTime);
|
||||
|
|
@ -167,78 +163,69 @@ function WidgetsEditor() {
|
|||
PerformanceTracker.stopTracking();
|
||||
return (
|
||||
<EditorContextProvider renderMode="CANVAS">
|
||||
{showOnboardingTasks ? (
|
||||
<OnboardingTasks />
|
||||
) : (
|
||||
<>
|
||||
{guidedTourEnabled && <Guide />}
|
||||
{guidedTourEnabled && <Guide />}
|
||||
<div className="relative flex flex-row w-full overflow-hidden">
|
||||
<div
|
||||
className={classNames({
|
||||
"relative flex flex-col w-full overflow-hidden": true,
|
||||
"m-8 border border-gray-200":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
>
|
||||
{!isAppSettingsPaneWithNavigationTabOpen && <CanvasTopSection />}
|
||||
<AnonymousDataPopup />
|
||||
<div
|
||||
className="relative flex flex-row w-full overflow-hidden"
|
||||
data-testid="widgets-editor"
|
||||
draggable
|
||||
id="widgets-editor"
|
||||
onClick={handleWrapperClick}
|
||||
onDragStart={onDragStart}
|
||||
style={{
|
||||
fontFamily: fontFamily,
|
||||
}}
|
||||
>
|
||||
{showNavigation()}
|
||||
|
||||
<div className="relative flex flex-row w-full overflow-hidden">
|
||||
<div
|
||||
<PageViewContainer
|
||||
className={classNames({
|
||||
"relative flex flex-col w-full overflow-hidden": true,
|
||||
"m-8 border border-gray-200":
|
||||
"relative flex flex-row w-full justify-center overflow-hidden":
|
||||
true,
|
||||
"select-none pointer-events-none":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
hasPinnedSidebar={
|
||||
isPreviewingNavigation && !isMobile
|
||||
? currentApplicationDetails?.applicationDetail
|
||||
?.navigationSetting?.orientation ===
|
||||
NAVIGATION_SETTINGS.ORIENTATION.SIDE && isAppSidebarPinned
|
||||
: false
|
||||
}
|
||||
isPreviewMode={isPreviewMode}
|
||||
isPublished={isPublished}
|
||||
sidebarWidth={isPreviewingNavigation ? sidebarWidth : 0}
|
||||
>
|
||||
{!isAppSettingsPaneWithNavigationTabOpen && <CanvasTopSection />}
|
||||
{shouldShowSnapShotBanner && (
|
||||
<div className="absolute top-0 z-1 w-full">
|
||||
<SnapShotBannerCTA />
|
||||
</div>
|
||||
)}
|
||||
<CanvasContainer
|
||||
isAppSettingsPaneWithNavigationTabOpen={
|
||||
AppSettingsTabs.Navigation === appSettingsPaneContext?.type
|
||||
}
|
||||
isPreviewMode={isPreviewMode}
|
||||
navigationHeight={navigationHeight}
|
||||
shouldShowSnapShotBanner={shouldShowSnapShotBanner}
|
||||
/>
|
||||
</PageViewContainer>
|
||||
|
||||
<div
|
||||
className="relative flex flex-row w-full overflow-hidden"
|
||||
data-testid="widgets-editor"
|
||||
draggable
|
||||
id="widgets-editor"
|
||||
onClick={handleWrapperClick}
|
||||
onDragStart={onDragStart}
|
||||
style={{
|
||||
fontFamily: fontFamily,
|
||||
}}
|
||||
>
|
||||
{showNavigation()}
|
||||
|
||||
<PageViewContainer
|
||||
className={classNames({
|
||||
"relative flex flex-row w-full justify-center overflow-hidden":
|
||||
true,
|
||||
"select-none pointer-events-none":
|
||||
isAppSettingsPaneWithNavigationTabOpen,
|
||||
})}
|
||||
hasPinnedSidebar={
|
||||
isPreviewingNavigation && !isMobile
|
||||
? currentApplicationDetails?.applicationDetail
|
||||
?.navigationSetting?.orientation ===
|
||||
NAVIGATION_SETTINGS.ORIENTATION.SIDE &&
|
||||
isAppSidebarPinned
|
||||
: false
|
||||
}
|
||||
isPreviewMode={isPreviewMode}
|
||||
isPublished={isPublished}
|
||||
sidebarWidth={isPreviewingNavigation ? sidebarWidth : 0}
|
||||
>
|
||||
{shouldShowSnapShotBanner && (
|
||||
<div className="absolute top-0 z-1 w-full">
|
||||
<SnapShotBannerCTA />
|
||||
</div>
|
||||
)}
|
||||
<CanvasContainer
|
||||
isAppSettingsPaneWithNavigationTabOpen={
|
||||
AppSettingsTabs.Navigation ===
|
||||
appSettingsPaneContext?.type
|
||||
}
|
||||
isPreviewMode={isPreviewMode}
|
||||
navigationHeight={navigationHeight}
|
||||
shouldShowSnapShotBanner={shouldShowSnapShotBanner}
|
||||
/>
|
||||
</PageViewContainer>
|
||||
|
||||
<CrudInfoModal />
|
||||
</div>
|
||||
<Debugger />
|
||||
</div>
|
||||
<PropertyPaneContainer />
|
||||
<CrudInfoModal />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Debugger />
|
||||
</div>
|
||||
<PropertyPaneContainer />
|
||||
</div>
|
||||
</EditorContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import { GIT_BRANCH_QUERY_KEY } from "constants/routes";
|
|||
import TemplatesModal from "pages/Templates/TemplatesModal";
|
||||
import ReconnectDatasourceModal from "./gitSync/ReconnectDatasourceModal";
|
||||
import { Spinner } from "design-system";
|
||||
import SignpostingOverlay from "pages/Editor/FirstTimeUserOnboarding/Overlay";
|
||||
|
||||
type EditorProps = {
|
||||
currentApplicationId?: string;
|
||||
|
|
@ -187,6 +188,7 @@ class Editor extends Component<Props> {
|
|||
<TemplatesModal />
|
||||
<ImportedApplicationSuccessModal />
|
||||
<ReconnectDatasourceModal />
|
||||
<SignpostingOverlay />
|
||||
</GlobalHotKeys>
|
||||
</div>
|
||||
<RequestConfirmationModal />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ReduxAction } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
||||
import type { SIGNPOSTING_STEP } from "pages/Editor/FirstTimeUserOnboarding/Utils";
|
||||
import { createReducer } from "utils/ReducerUtils";
|
||||
|
||||
const initialState: OnboardingState = {
|
||||
|
|
@ -9,6 +10,16 @@ const initialState: OnboardingState = {
|
|||
firstTimeUserOnboardingApplicationIds: [],
|
||||
firstTimeUserOnboardingComplete: false,
|
||||
showFirstTimeUserOnboardingModal: false,
|
||||
setOverlay: false,
|
||||
stepState: [],
|
||||
showSignpostingTooltip: false,
|
||||
showAnonymousDataPopup: false,
|
||||
};
|
||||
|
||||
export type StepState = {
|
||||
step: SIGNPOSTING_STEP;
|
||||
completed: boolean;
|
||||
read?: boolean;
|
||||
};
|
||||
|
||||
export interface OnboardingState {
|
||||
|
|
@ -17,6 +28,10 @@ export interface OnboardingState {
|
|||
firstTimeUserOnboardingApplicationIds: string[];
|
||||
firstTimeUserOnboardingComplete: boolean;
|
||||
showFirstTimeUserOnboardingModal: boolean;
|
||||
stepState: StepState[];
|
||||
setOverlay: boolean;
|
||||
showSignpostingTooltip: boolean;
|
||||
showAnonymousDataPopup: boolean;
|
||||
}
|
||||
|
||||
const onboardingReducer = createReducer(initialState, {
|
||||
|
|
@ -62,6 +77,65 @@ const onboardingReducer = createReducer(initialState, {
|
|||
) => {
|
||||
return { ...state, forceOpenWidgetPanel: action.payload };
|
||||
},
|
||||
[ReduxActionTypes.SIGNPOSTING_STEP_UPDATE]: (
|
||||
state: OnboardingState,
|
||||
action: ReduxAction<StepState>,
|
||||
) => {
|
||||
const index = state.stepState.findIndex(
|
||||
(stepState) => stepState.step === action.payload.step,
|
||||
);
|
||||
const newArray = [...state.stepState];
|
||||
if (index >= 0) {
|
||||
newArray[index] = action.payload;
|
||||
} else {
|
||||
newArray.push(action.payload);
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
stepState: newArray,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SIGNPOSTING_MARK_ALL_READ]: (state: OnboardingState) => {
|
||||
return {
|
||||
...state,
|
||||
stepState: state.stepState.map((step) => {
|
||||
if (step.completed) {
|
||||
return {
|
||||
...step,
|
||||
read: true,
|
||||
};
|
||||
}
|
||||
return step;
|
||||
}),
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SET_SIGNPOSTING_OVERLAY]: (
|
||||
state: OnboardingState,
|
||||
action: ReduxAction<boolean>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
setOverlay: action.payload,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SIGNPOSTING_SHOW_TOOLTIP]: (
|
||||
state: OnboardingState,
|
||||
action: ReduxAction<boolean>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
showSignpostingTooltip: action.payload,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SHOW_ANONYMOUS_DATA_POPUP]: (
|
||||
state: OnboardingState,
|
||||
action: ReduxAction<boolean>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
showAnonymousDataPopup: action.payload,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default onboardingReducer;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import type { ApplicationPagePayload } from "@appsmith/api/ApplicationApi";
|
|||
import { updateSlugNamesInURL } from "utils/helpers";
|
||||
import { generateAutoHeightLayoutTreeAction } from "actions/autoHeightActions";
|
||||
import { safeCrashAppRequest } from "../actions/errorActions";
|
||||
import { resetSnipingMode } from "actions/propertyPaneActions";
|
||||
|
||||
export const URL_CHANGE_ACTIONS = [
|
||||
ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE,
|
||||
|
|
@ -107,6 +108,7 @@ function* resetEditorSaga() {
|
|||
// might end up in preview mode if they were in preview mode
|
||||
// previously
|
||||
yield put(setPreviewModeAction(false));
|
||||
yield put(resetSnipingMode());
|
||||
yield put(resetEditorSuccess());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from "redux-saga/effects";
|
||||
import {
|
||||
getFirstTimeUserOnboardingApplicationIds,
|
||||
getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown,
|
||||
removeAllFirstTimeUserOnboardingApplicationIds,
|
||||
removeFirstTimeUserOnboardingApplicationId,
|
||||
setEnableStartSignposting,
|
||||
|
|
@ -29,6 +30,7 @@ import {
|
|||
getHadReachedStep,
|
||||
getOnboardingWorkspaces,
|
||||
getQueryAction,
|
||||
getSignpostingStepStateByStep,
|
||||
getTableWidget,
|
||||
} from "selectors/onboardingSelectors";
|
||||
import type { Workspaces } from "@appsmith/constants/workspaceConstants";
|
||||
|
|
@ -39,6 +41,9 @@ import {
|
|||
loadGuidedTour,
|
||||
removeFirstTimeUserOnboardingApplicationId as removeFirstTimeUserOnboardingApplicationIdAction,
|
||||
setCurrentStep,
|
||||
setSignpostingOverlay,
|
||||
showSignpostingTooltip,
|
||||
signpostingStepUpdate,
|
||||
toggleLoader,
|
||||
} from "actions/onboardingActions";
|
||||
import {
|
||||
|
|
@ -77,13 +82,11 @@ import { builderURL, queryEditorIdURL } from "RouteBuilder";
|
|||
import { GuidedTourEntityNames } from "pages/Editor/GuidedTour/constants";
|
||||
import type { GuidedTourState } from "reducers/uiReducers/guidedTourReducer";
|
||||
import { sessionStorage } from "utils/localStorage";
|
||||
import store from "store";
|
||||
import {
|
||||
createMessage,
|
||||
ONBOARDING_SKIPPED_FIRST_TIME_USER,
|
||||
} from "@appsmith/constants/messages";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
import { toast } from "design-system";
|
||||
import type { SIGNPOSTING_STEP } from "pages/Editor/FirstTimeUserOnboarding/Utils";
|
||||
import type { StepState } from "reducers/uiReducers/onBoardingReducer";
|
||||
import { isUndefined } from "lodash";
|
||||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
|
||||
const GUIDED_TOUR_STORAGE_KEY = "GUIDED_TOUR_STORAGE_KEY";
|
||||
|
||||
|
|
@ -416,25 +419,6 @@ function* endFirstTimeUserOnboardingSaga() {
|
|||
firstTimeUserExperienceAppId,
|
||||
),
|
||||
);
|
||||
toast.show(createMessage(ONBOARDING_SKIPPED_FIRST_TIME_USER), {
|
||||
kind: "success",
|
||||
action: {
|
||||
text: "undo",
|
||||
effect: () => {
|
||||
store.dispatch({
|
||||
type: ReduxActionTypes.UNDO_END_FIRST_TIME_USER_ONBOARDING,
|
||||
payload: firstTimeUserExperienceAppId,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function* undoEndFirstTimeUserOnboardingSaga(action: ReduxAction<string>) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_APPLICATION_ID,
|
||||
payload: action.payload,
|
||||
});
|
||||
}
|
||||
|
||||
function* firstTimeUserOnboardingInitSaga(
|
||||
|
|
@ -445,15 +429,38 @@ function* firstTimeUserOnboardingInitSaga(
|
|||
type: ReduxActionTypes.SET_FIRST_TIME_USER_ONBOARDING_APPLICATION_ID,
|
||||
payload: action.payload.applicationId,
|
||||
});
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
|
||||
payload: true,
|
||||
});
|
||||
history.replace(
|
||||
builderURL({
|
||||
pageId: action.payload.pageId,
|
||||
}),
|
||||
);
|
||||
|
||||
const isEditorInitialised: boolean = yield select(getIsEditorInitialized);
|
||||
if (!isEditorInitialised) {
|
||||
yield take(ReduxActionTypes.INITIALIZE_EDITOR_SUCCESS);
|
||||
}
|
||||
|
||||
let showOverlay = true;
|
||||
|
||||
// We don't want to show the signposting overlay when we intend to show the
|
||||
// telemetry callout
|
||||
const currentUser: User | undefined = yield select(getCurrentUser);
|
||||
if (currentUser?.isSuperUser && !isAirgapped()) {
|
||||
const isAnonymousDataPopupAlreadyOpen: unknown = yield call(
|
||||
getFirstTimeUserOnboardingTelemetryCalloutIsAlreadyShown,
|
||||
);
|
||||
if (!isAnonymousDataPopupAlreadyOpen) {
|
||||
showOverlay = false;
|
||||
}
|
||||
}
|
||||
|
||||
yield put(setSignpostingOverlay(showOverlay));
|
||||
// Show the modal once the editor is loaded. The delay is to grab user attention back once the editor
|
||||
yield delay(1000);
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_SHOW_FIRST_TIME_USER_ONBOARDING_MODAL,
|
||||
payload: true,
|
||||
});
|
||||
}
|
||||
|
||||
function* setFirstTimeUserOnboardingCompleteSaga(action: ReduxAction<boolean>) {
|
||||
|
|
@ -467,6 +474,38 @@ function* disableStartFirstTimeUserOnboardingSaga() {
|
|||
yield call(setEnableStartSignposting, false);
|
||||
}
|
||||
|
||||
function* setSignpostingStepStateSaga(
|
||||
action: ReduxAction<{ step: SIGNPOSTING_STEP; completed: boolean }>,
|
||||
) {
|
||||
const { completed, step } = action.payload;
|
||||
const stepState: StepState | undefined = yield select(
|
||||
getSignpostingStepStateByStep,
|
||||
step,
|
||||
);
|
||||
|
||||
// No changes to update so we ignore
|
||||
if (stepState && stepState.completed === completed) return;
|
||||
|
||||
const readProps = completed
|
||||
? {
|
||||
read: false,
|
||||
}
|
||||
: {};
|
||||
yield put(
|
||||
signpostingStepUpdate({
|
||||
...action.payload,
|
||||
...readProps,
|
||||
}),
|
||||
);
|
||||
|
||||
// Show tooltip when a step is completed
|
||||
if (!isUndefined(readProps.read) && !readProps.read) {
|
||||
// Show tooltip after a small delay to not be abrupt
|
||||
yield delay(1000);
|
||||
yield put(showSignpostingTooltip(true));
|
||||
}
|
||||
}
|
||||
|
||||
export default function* onboardingActionSagas() {
|
||||
yield all([
|
||||
takeLatest(
|
||||
|
|
@ -500,10 +539,6 @@ export default function* onboardingActionSagas() {
|
|||
ReduxActionTypes.END_FIRST_TIME_USER_ONBOARDING,
|
||||
endFirstTimeUserOnboardingSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.UNDO_END_FIRST_TIME_USER_ONBOARDING,
|
||||
undoEndFirstTimeUserOnboardingSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.FIRST_TIME_USER_ONBOARDING_INIT,
|
||||
firstTimeUserOnboardingInitSaga,
|
||||
|
|
@ -516,5 +551,9 @@ export default function* onboardingActionSagas() {
|
|||
ReduxActionTypes.DISABLE_START_SIGNPOSTING,
|
||||
disableStartFirstTimeUserOnboardingSaga,
|
||||
),
|
||||
takeLatest(
|
||||
ReduxActionTypes.SIGNPOSTING_STEP_UPDATE_INIT,
|
||||
setSignpostingStepStateSaga,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import { denormalize } from "utils/canvasStructureHelpers";
|
|||
import { isAutoHeightEnabledForWidget } from "widgets/WidgetUtils";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
import { getIsAnonymousDataPopupVisible } from "./onboardingSelectors";
|
||||
|
||||
const getIsDraggingOrResizing = (state: AppState) =>
|
||||
state.ui.widgetDragResize.isResizing || state.ui.widgetDragResize.isDragging;
|
||||
|
|
@ -978,14 +979,16 @@ export const showCanvasTopSectionSelector = createSelector(
|
|||
getCanvasWidgets,
|
||||
previewModeSelector,
|
||||
getCurrentPageId,
|
||||
(canvasWidgets, inPreviewMode, pageId) => {
|
||||
getIsAnonymousDataPopupVisible,
|
||||
(canvasWidgets, inPreviewMode, pageId, isAnonymousDataPopupVisible) => {
|
||||
const state = JSON.parse(
|
||||
localStorage.getItem(LOCAL_STORAGE_KEYS.CANVAS_CARDS_STATE) ?? "{}",
|
||||
);
|
||||
if (
|
||||
!state[pageId] ||
|
||||
Object.keys(canvasWidgets).length > 1 ||
|
||||
inPreviewMode
|
||||
inPreviewMode ||
|
||||
isAnonymousDataPopupVisible
|
||||
)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,13 @@ export const getDatasources = (state: AppState): Datasource[] => {
|
|||
return state.entities.datasources.list;
|
||||
};
|
||||
|
||||
// Returns non temp datasources
|
||||
export const getSavedDatasources = (state: AppState): Datasource[] => {
|
||||
return state.entities.datasources.list.filter(
|
||||
(datasource) => datasource.id !== TEMP_DATASOURCE_ID,
|
||||
);
|
||||
};
|
||||
|
||||
export const getRecentDatasourceIds = (state: AppState): string[] => {
|
||||
return state.entities.datasources.recentDatasources;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ import type { AppState } from "@appsmith/reducers";
|
|||
import { createSelector } from "reselect";
|
||||
import { getUserApplicationsWorkspaces } from "@appsmith/selectors/applicationSelectors";
|
||||
import { getWidgets } from "sagas/selectors";
|
||||
import {
|
||||
getActionResponses,
|
||||
getActions,
|
||||
getCanvasWidgets,
|
||||
} from "./entitiesSelector";
|
||||
import { getActionResponses, getActions } from "./entitiesSelector";
|
||||
import { getLastSelectedWidget } from "./ui";
|
||||
import { GuidedTourEntityNames } from "pages/Editor/GuidedTour/constants";
|
||||
import type { SIGNPOSTING_STEP } from "pages/Editor/FirstTimeUserOnboarding/Utils";
|
||||
import { isBoolean } from "lodash";
|
||||
|
||||
// Signposting selectors
|
||||
|
||||
|
|
@ -38,29 +36,28 @@ export const getInOnboardingWidgetSelection = (state: AppState) =>
|
|||
export const getIsOnboardingWidgetSelection = (state: AppState) =>
|
||||
state.ui.onBoarding.inOnboardingWidgetSelection;
|
||||
|
||||
const previewModeSelector = (state: AppState) => {
|
||||
return state.ui.editor.isPreviewMode;
|
||||
};
|
||||
|
||||
export const getIsOnboardingTasksView = createSelector(
|
||||
getCanvasWidgets,
|
||||
getIsFirstTimeUserOnboardingEnabled,
|
||||
getIsOnboardingWidgetSelection,
|
||||
previewModeSelector,
|
||||
(
|
||||
widgets,
|
||||
enableFirstTimeUserOnboarding,
|
||||
isOnboardingWidgetSelection,
|
||||
inPreviewMode,
|
||||
) => {
|
||||
return (
|
||||
Object.keys(widgets).length == 1 &&
|
||||
enableFirstTimeUserOnboarding &&
|
||||
!isOnboardingWidgetSelection &&
|
||||
!inPreviewMode
|
||||
);
|
||||
export const getSignpostingStepState = (state: AppState) =>
|
||||
state.ui.onBoarding.stepState;
|
||||
export const getSignpostingStepStateByStep = createSelector(
|
||||
getSignpostingStepState,
|
||||
(_state: AppState, step: SIGNPOSTING_STEP) => step,
|
||||
(stepState, step) => {
|
||||
return stepState.find((state) => state.step === step);
|
||||
},
|
||||
);
|
||||
export const getSignpostingUnreadSteps = createSelector(
|
||||
getSignpostingStepState,
|
||||
(stepState) => {
|
||||
if (!stepState.length) return [];
|
||||
return stepState.filter((state) => isBoolean(state.read) && !state.read);
|
||||
},
|
||||
);
|
||||
export const getSignpostingSetOverlay = (state: AppState) =>
|
||||
state.ui.onBoarding.setOverlay;
|
||||
export const getSignpostingTooltipVisible = (state: AppState) =>
|
||||
state.ui.onBoarding.showSignpostingTooltip;
|
||||
export const getIsAnonymousDataPopupVisible = (state: AppState) =>
|
||||
state.ui.onBoarding.showAnonymousDataPopup;
|
||||
|
||||
// Guided Tour selectors
|
||||
export const isExploringSelector = (state: AppState) =>
|
||||
|
|
|
|||
|
|
@ -187,13 +187,14 @@ export type EventName =
|
|||
| "SNIPPET_COPIED"
|
||||
| "SNIPPET_LOOKUP"
|
||||
| "SIGNPOSTING_SKIP"
|
||||
| "SIGNPOSTING_CREATE_DATASOURCE_CLICK"
|
||||
| "SIGNPOSTING_CREATE_QUERY_CLICK"
|
||||
| "SIGNPOSTING_ADD_WIDGET_CLICK"
|
||||
| "SIGNPOSTING_CONNECT_WIDGET_CLICK"
|
||||
| "SIGNPOSTING_PUBLISH_CLICK"
|
||||
| "SIGNPOSTING_BUILD_APP_CLICK"
|
||||
| "SIGNPOSTING_MODAL_CREATE_DATASOURCE_CLICK"
|
||||
| "SIGNPOSTING_MODAL_CREATE_QUERY_CLICK"
|
||||
| "SIGNPOSTING_MODAL_ADD_WIDGET_CLICK"
|
||||
| "SIGNPOSTING_MODAL_CONNECT_WIDGET_CLICK"
|
||||
| "SIGNPOSTING_MODAL_PUBLISH_CLICK"
|
||||
| "SIGNPOSTING_WELCOME_TOUR_CLICK"
|
||||
| "SIGNPOSTING_MODAL_CLOSE_CLICK"
|
||||
| "SIGNPOSTING_INFO_CLICK"
|
||||
| "GS_BRANCH_MORE_MENU_OPEN"
|
||||
| "GIT_DISCARD_WARNING"
|
||||
| "GIT_DISCARD_CANCEL"
|
||||
|
|
@ -322,6 +323,7 @@ export type EventName =
|
|||
| "GOOGLE_SHEET_FILE_PICKER_CANCEL"
|
||||
| "GOOGLE_SHEET_FILE_PICKER_PICKED"
|
||||
| "TELEMETRY_DISABLED"
|
||||
| "HELP_MENU_WELCOME_TOUR_CLICK"
|
||||
| "DISPLAY_TELEMETRY_CALLOUT"
|
||||
| "VISIT_ADMIN_SETTINGS_TELEMETRY_CALLOUT"
|
||||
| "LEARN_MORE_TELEMETRY_CALLOUT"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ let cachedLottie: LottiePlayer | null = null;
|
|||
|
||||
export type LazyAnimationItem = Pick<
|
||||
AnimationItem,
|
||||
"play" | "addEventListener" | "destroy"
|
||||
"play" | "addEventListener" | "destroy" | "goToAndStop"
|
||||
>;
|
||||
|
||||
const lazyLottie = {
|
||||
|
|
@ -32,7 +32,7 @@ const lazyLottie = {
|
|||
|
||||
const abortController = new AbortController();
|
||||
const queuedCommands: Array<{
|
||||
commandName: "play" | "addEventListener";
|
||||
commandName: "play" | "addEventListener" | "goToAndStop";
|
||||
args: any[];
|
||||
}> = [];
|
||||
|
||||
|
|
@ -60,6 +60,9 @@ const lazyLottie = {
|
|||
throw new Error("Not implemented");
|
||||
};
|
||||
},
|
||||
goToAndStop(...args) {
|
||||
queuedCommands.push({ commandName: "goToAndStop", args });
|
||||
},
|
||||
destroy() {
|
||||
abortController.abort();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const STORAGE_KEYS: {
|
|||
APP_THEMING_BETA_SHOWN: "APP_THEMING_BETA_SHOWN",
|
||||
FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY:
|
||||
"FIRST_TIME_USER_ONBOARDING_TELEMETRY_CALLOUT_VISIBILITY",
|
||||
SIGNPOSTING_APP_STATE: "SIGNPOSTING_APP_STATE",
|
||||
};
|
||||
|
||||
const store = localforage.createInstance({
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user