diff --git a/packages/builder/src/analytics.js b/packages/builder/src/analytics.js index 60a41e42c2..a7166f00f6 100644 --- a/packages/builder/src/analytics.js +++ b/packages/builder/src/analytics.js @@ -6,6 +6,10 @@ let analyticsEnabled const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL const sentryConfigured = process.env.SENTRY_DSN +const FEEDBACK_SUBMITTED_KEY = "budibase:feedback_submitted" +const APP_FIRST_STARTED_KEY = "budibase:first_run" +const feedbackHours = 12 + async function activate() { if (analyticsEnabled === undefined) { // only the server knows the true NODE_ENV @@ -62,10 +66,50 @@ function captureEvent(eventName, props = {}) { posthog.capture(eventName, props) } +if (!localStorage.getItem(APP_FIRST_STARTED_KEY)) { + localStorage.setItem(APP_FIRST_STARTED_KEY, Date.now()) +} + +const isFeedbackTimeElapsed = sinceDateStr => { + const sinceDate = parseFloat(sinceDateStr) + const feedbackMilliseconds = feedbackHours * 60 * 60 * 1000 + return Date.now() > sinceDate + feedbackMilliseconds +} +function submitFeedback(values) { + if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return + localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now()) + + const prefixedValues = Object.entries(values).reduce((obj, [key, value]) => { + obj[`feedback_${key}`] = value + return obj + }, {}) + + posthog.capture("Feedback Submitted", prefixedValues) +} + +function requestFeedbackOnDeploy() { + if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false + const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) + if (!lastSubmittedStr) return true + return isFeedbackTimeElapsed(lastSubmittedStr) +} + +function highlightFeedbackIcon() { + if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false + const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) + if (lastSubmittedStr) return isFeedbackTimeElapsed(lastSubmittedStr) + const firstRunStr = localStorage.getItem(APP_FIRST_STARTED_KEY) + if (!firstRunStr) return false + return isFeedbackTimeElapsed(firstRunStr) +} + export default { activate, identify, identifyByApiKey, captureException, captureEvent, + requestFeedbackOnDeploy, + submitFeedback, + highlightFeedbackIcon, } diff --git a/packages/builder/src/components/common/Icons/Feedback.svelte b/packages/builder/src/components/common/Icons/Feedback.svelte new file mode 100644 index 0000000000..e34415d612 --- /dev/null +++ b/packages/builder/src/components/common/Icons/Feedback.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/packages/builder/src/components/common/Icons/index.js b/packages/builder/src/components/common/Icons/index.js index 7082b1b5e0..f11188ee34 100644 --- a/packages/builder/src/components/common/Icons/index.js +++ b/packages/builder/src/components/common/Icons/index.js @@ -33,3 +33,4 @@ export { default as InfoIcon } from "./Info.svelte" export { default as CloseIcon } from "./Close.svelte" export { default as MoreIcon } from "./More.svelte" export { default as CloseCircleIcon } from "./CloseCircle.svelte" +export { default as FeedbackIcon } from "./Feedback.svelte" diff --git a/packages/builder/src/components/userInterface/Feedback/FeedbackIframe.svelte b/packages/builder/src/components/userInterface/Feedback/FeedbackIframe.svelte new file mode 100644 index 0000000000..17c6059de4 --- /dev/null +++ b/packages/builder/src/components/userInterface/Feedback/FeedbackIframe.svelte @@ -0,0 +1,59 @@ + + + + + diff --git a/packages/builder/src/components/userInterface/Feedback/FeedbackNavLink.svelte b/packages/builder/src/components/userInterface/Feedback/FeedbackNavLink.svelte new file mode 100644 index 0000000000..5299e7d5c0 --- /dev/null +++ b/packages/builder/src/components/userInterface/Feedback/FeedbackNavLink.svelte @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/packages/builder/src/pages/[application]/_reset.svelte b/packages/builder/src/pages/[application]/_reset.svelte index 87759aef97..fb2b233e2c 100644 --- a/packages/builder/src/pages/[application]/_reset.svelte +++ b/packages/builder/src/pages/[application]/_reset.svelte @@ -2,6 +2,7 @@ import { store, automationStore, backendUiStore } from "builderStore" import { Button } from "@budibase/bbui" import SettingsLink from "components/settings/Link.svelte" + import FeedbackNavLink from "components/userInterface/Feedback/FeedbackNavLink.svelte" import { get } from "builderStore/api" import { isActive, goto, layout } from "@sveltech/routify" import { PreviewIcon } from "components/common/Icons/" @@ -65,6 +66,7 @@ {/each}
+ import { onMount } from "svelte" - import { Button, Spacer } from "@budibase/bbui" + import { Button, Spacer, Modal } from "@budibase/bbui" import { store } from "builderStore" import { notifier } from "builderStore/store/notifications" import api from "builderStore/api" import Spinner from "components/common/Spinner.svelte" import DeploymentHistory from "components/deploy/DeploymentHistory.svelte" import analytics from "analytics" + import FeedbackIframe from "components/userInterface/Feedback/FeedbackIframe.svelte" let loading = false let deployments = [] let poll + let feedbackModal $: appId = $store.appId @@ -31,6 +33,10 @@ analytics.captureEvent("Deployed App", { appId, }) + + if (analytics.requestFeedbackOnDeploy()) { + feedbackModal.show() + } } catch (err) { analytics.captureEvent("Deploy App Failed", { appId, @@ -57,6 +63,9 @@ src="/_builder/assets/deploy-rocket.jpg" alt="Rocket flying through sky" /> + + feedbackModal.hide()} /> +