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 @@
+
+
+
+