Merge pull request #778 from Budibase/feedback

Feedback
This commit is contained in:
Michael Shanks 2020-10-28 09:45:02 +00:00 committed by GitHub
commit 5d3dc58495
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 183 additions and 1 deletions

View File

@ -6,6 +6,10 @@ let analyticsEnabled
const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL
const sentryConfigured = process.env.SENTRY_DSN 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() { async function activate() {
if (analyticsEnabled === undefined) { if (analyticsEnabled === undefined) {
// only the server knows the true NODE_ENV // only the server knows the true NODE_ENV
@ -62,10 +66,50 @@ function captureEvent(eventName, props = {}) {
posthog.capture(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 { export default {
activate, activate,
identify, identify,
identifyByApiKey, identifyByApiKey,
captureException, captureException,
captureEvent, captureEvent,
requestFeedbackOnDeploy,
submitFeedback,
highlightFeedbackIcon,
} }

View File

@ -0,0 +1,11 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="24"
height="24">
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="currentColor"
d="M6.455 19L2 22.5V4a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v14a1 1 0 0 1-1
1H6.455zM11 13v2h2v-2h-2zm0-6v5h2V7h-2z" />
</svg>

After

Width:  |  Height:  |  Size: 292 B

View File

@ -33,3 +33,4 @@ export { default as InfoIcon } from "./Info.svelte"
export { default as CloseIcon } from "./Close.svelte" export { default as CloseIcon } from "./Close.svelte"
export { default as MoreIcon } from "./More.svelte" export { default as MoreIcon } from "./More.svelte"
export { default as CloseCircleIcon } from "./CloseCircle.svelte" export { default as CloseCircleIcon } from "./CloseCircle.svelte"
export { default as FeedbackIcon } from "./Feedback.svelte"

View File

@ -0,0 +1,59 @@
<script>
import analytics from "analytics"
import { createEventDispatcher } from "svelte"
import { store } from "builderStore"
const dispatch = createEventDispatcher()
const feedbackUrl = "https://feedback.budibase.com"
let iframe
// the app @ feedback.budibase.com expects to be loaded
// in an iframe, and posts messages back.
// this means that we can submit using the Builder's posthog setup
window.addEventListener(
"message",
function(ev) {
if (ev.origin !== feedbackUrl) return
if (ev.data.type === "loaded") {
iframe.setAttribute(
"style",
`height:${ev.data.height}px; width:${ev.data.width}px`
)
} else if (ev.data.type === "submitted") {
analytics.submitFeedback(ev.data.data)
$store.highlightFeedbackIcon = false
dispatch("finished")
}
},
false
)
</script>
<iframe src={feedbackUrl} title="feedback" bind:this={iframe}>
<html lang="html">
<style>
body {
display: flex;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
}
</style>
<body>
<div>Loading...</div>
</body>
</html>
</iframe>
<style>
iframe {
border-style: none;
height: auto;
overflow-y: hidden;
overflow-x: hidden;
min-width: 500px;
}
</style>

View File

@ -0,0 +1,56 @@
<script>
import { FeedbackIcon } from "components/common/Icons/"
import { Popover } from "@budibase/bbui"
import { store } from "builderStore"
import FeedbackIframe from "./FeedbackIframe.svelte"
import analytics from "analytics"
const FIVE_MINUTES = 300000
let iconContainer
let popover
setInterval(() => {
$store.highlightFeedbackIcon = analytics.highlightFeedbackIcon()
}, FIVE_MINUTES)
</script>
<span
class="container"
bind:this={iconContainer}
on:click={popover.show}
class:highlight={$store.highlightFeedbackIcon}>
<FeedbackIcon />
</span>
<Popover bind:this={popover} anchor={iconContainer} align="right">
<FeedbackIframe on:finished={popover.hide} />
</Popover>
<style>
.container {
cursor: pointer;
color: var(--grey-7);
margin: 0 20px 0 0;
font-weight: 500;
font-size: 1rem;
height: 100%;
display: flex;
flex: 1;
align-items: center;
box-sizing: border-box;
}
.container:hover {
color: var(--ink);
font-weight: 500;
}
.highlight {
color: var(--blue);
}
.highlight > :global(svg) {
filter: drop-shadow(0 0 20px var(--blue));
}
</style>

View File

@ -2,6 +2,7 @@
import { store, automationStore, backendUiStore } from "builderStore" import { store, automationStore, backendUiStore } from "builderStore"
import { Button } from "@budibase/bbui" import { Button } from "@budibase/bbui"
import SettingsLink from "components/settings/Link.svelte" import SettingsLink from "components/settings/Link.svelte"
import FeedbackNavLink from "components/userInterface/Feedback/FeedbackNavLink.svelte"
import { get } from "builderStore/api" import { get } from "builderStore/api"
import { isActive, goto, layout } from "@sveltech/routify" import { isActive, goto, layout } from "@sveltech/routify"
import { PreviewIcon } from "components/common/Icons/" import { PreviewIcon } from "components/common/Icons/"
@ -65,6 +66,7 @@
{/each} {/each}
</div> </div>
<div class="toprightnav"> <div class="toprightnav">
<FeedbackNavLink />
<SettingsLink /> <SettingsLink />
<span <span
class:active={false} class:active={false}

View File

@ -1,16 +1,18 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { Button, Spacer } from "@budibase/bbui" import { Button, Spacer, Modal } from "@budibase/bbui"
import { store } from "builderStore" import { store } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import api from "builderStore/api" import api from "builderStore/api"
import Spinner from "components/common/Spinner.svelte" import Spinner from "components/common/Spinner.svelte"
import DeploymentHistory from "components/deploy/DeploymentHistory.svelte" import DeploymentHistory from "components/deploy/DeploymentHistory.svelte"
import analytics from "analytics" import analytics from "analytics"
import FeedbackIframe from "components/userInterface/Feedback/FeedbackIframe.svelte"
let loading = false let loading = false
let deployments = [] let deployments = []
let poll let poll
let feedbackModal
$: appId = $store.appId $: appId = $store.appId
@ -31,6 +33,10 @@
analytics.captureEvent("Deployed App", { analytics.captureEvent("Deployed App", {
appId, appId,
}) })
if (analytics.requestFeedbackOnDeploy()) {
feedbackModal.show()
}
} catch (err) { } catch (err) {
analytics.captureEvent("Deploy App Failed", { analytics.captureEvent("Deploy App Failed", {
appId, appId,
@ -57,6 +63,9 @@
src="/_builder/assets/deploy-rocket.jpg" src="/_builder/assets/deploy-rocket.jpg"
alt="Rocket flying through sky" /> alt="Rocket flying through sky" />
</section> </section>
<Modal bind:this={feedbackModal}>
<FeedbackIframe on:finished={() => feedbackModal.hide()} />
</Modal>
<DeploymentHistory {appId} /> <DeploymentHistory {appId} />
<style> <style>