Merge remote-tracking branch 'origin/main' into refactor/ui

This commit is contained in:
liyasthomas
2021-07-08 13:44:00 +05:30
18 changed files with 346 additions and 52 deletions

View File

@@ -13,6 +13,7 @@ STORAGE_BUCKET=postwoman-api.appspot.com
MESSAGING_SENDER_ID=421993993223
APP_ID=1:421993993223:web:ec0baa8ee8c02ffa1fc6a2
MEASUREMENT_ID=G-ERJ6025CEB
FB_MEASUREMENT_ID=G-BBJ3R80PJT
# Base URL
BASE_URL=https://hoppscotch.io

View File

@@ -131,6 +131,7 @@
import { Splitpanes, Pane } from "splitpanes"
import Paho from "paho-mqtt"
import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default {
components: { Splitpanes, Pane },
@@ -202,6 +203,10 @@ export default {
})
this.client.onConnectionLost = this.onConnectionLost
this.client.onMessageArrived = this.onMessageArrived
logHoppRequestRunToAnalytics({
platform: "mqtt",
})
},
onConnectionFailure() {
this.connectionState = false

View File

@@ -153,6 +153,7 @@ import { Splitpanes, Pane } from "splitpanes"
import { io as Client } from "socket.io-client"
import wildcard from "socketio-wildcard"
import debounce from "~/helpers/utils/debounce"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default {
components: { Splitpanes, Pane },
@@ -275,6 +276,10 @@ export default {
icon: "error",
})
}
logHoppRequestRunToAnalytics({
platform: "socketio",
})
},
disconnect() {
this.io.close()

View File

@@ -53,6 +53,7 @@
<script>
import { Splitpanes, Pane } from "splitpanes"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import debounce from "~/helpers/utils/debounce"
export default {
@@ -164,6 +165,10 @@ export default {
},
]
}
logHoppRequestRunToAnalytics({
platform: "mqtt",
})
},
handleSSEError(error) {
this.stop()

View File

@@ -169,6 +169,7 @@
<script>
import { Splitpanes, Pane } from "splitpanes"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import debounce from "~/helpers/utils/debounce"
import "splitpanes/dist/splitpanes.css"
@@ -285,6 +286,10 @@ export default {
icon: "error",
})
}
logHoppRequestRunToAnalytics({
platform: "wss",
})
},
disconnect() {
if (this.socket) {

View File

@@ -21,36 +21,34 @@
</div>
</template>
<script>
import { getLocalConfig, setLocalConfig } from "~/newstore/localpersistence"
<script lang="ts">
import Vue from "vue"
import {
HoppAccentColors,
HoppAccentColor,
getSettingSubject,
settingsStore,
applySetting,
} from "~/newstore/settings"
export default {
export default Vue.extend({
data() {
return {
active: getLocalConfig("THEME_COLOR") || "green",
accentColors: [
"blue",
"green",
"teal",
"indigo",
"purple",
"orange",
"pink",
"red",
"yellow",
],
accentColors: HoppAccentColors,
active: settingsStore.value.THEME_COLOR,
}
},
watch: {
active(color) {
setLocalConfig("THEME_COLOR", color)
},
subscriptions() {
return {
active: getSettingSubject("THEME_COLOR"),
}
},
methods: {
setActiveColor(color) {
setActiveColor(color: HoppAccentColor) {
document.documentElement.setAttribute("data-accent", color)
this.active = color
applySetting("THEME_COLOR", color)
},
},
}
})
</script>

View File

@@ -15,15 +15,31 @@
</div>
</template>
<script>
export default {
<script lang="ts">
import Vue from "vue"
import {
applySetting,
getSettingSubject,
HoppBgColor,
HoppBgColors,
} from "~/newstore/settings"
export default Vue.extend({
data() {
return {
colors: ["system", "light", "dark", "black"],
colors: HoppBgColors,
}
},
subscriptions() {
return {
activeColor: getSettingSubject("BG_COLOR"),
}
},
methods: {
getIcon(color) {
setBGMode(color: HoppBgColor) {
applySetting("BG_COLOR", color)
},
getIcon(color: HoppBgColor) {
switch (color) {
case "system":
return "desktop_windows"
@@ -38,5 +54,5 @@ export default {
}
},
},
}
})
</script>

93
helpers/fb/analytics.ts Normal file
View File

@@ -0,0 +1,93 @@
import firebase from "firebase"
import { authEvents$ } from "./auth"
import {
HoppAccentColor,
HoppBgColor,
settings$,
settingsStore,
} from "~/newstore/settings"
let analytics: firebase.analytics.Analytics
type SettingsCustomDimensions = {
usesProxy: boolean
usesExtension: boolean
usesScrollInto: boolean
syncCollections: boolean
syncEnvironments: boolean
syncHistory: boolean
usesBg: HoppBgColor
usesAccent: HoppAccentColor
usesTelemetry: boolean
}
type HoppRequestEvent =
| {
platform: "rest" | "graphql-query" | "graphql-schema"
strategy: "normal" | "proxy" | "extension"
}
| { platform: "wss" | "sse" | "socketio" | "mqtt" }
export function initAnalytics() {
analytics = firebase.app().analytics()
initLoginListeners()
initSettingsListeners()
}
function initLoginListeners() {
authEvents$.subscribe((ev) => {
if (ev.event === "login") {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.setUserId(ev.user.uid)
analytics.logEvent("login", {
method: ev.user.providerData[0]?.providerId, // Assume the first provider is the login provider
})
}
} else if (ev.event === "logout") {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.logEvent("logout")
}
}
})
}
function initSettingsListeners() {
// Keep track of the telemetry status
let telemetryStatus = settingsStore.value.TELEMETRY_ENABLED
settings$.subscribe((settings) => {
const conf: SettingsCustomDimensions = {
usesProxy: settings.PROXY_ENABLED,
usesExtension: settings.EXTENSIONS_ENABLED,
usesScrollInto: settings.SCROLL_INTO_ENABLED,
syncCollections: settings.syncCollections,
syncEnvironments: settings.syncEnvironments,
syncHistory: settings.syncHistory,
usesAccent: settings.THEME_COLOR,
usesBg: settings.BG_COLOR,
usesTelemetry: settings.TELEMETRY_ENABLED,
}
// User toggled telemetry mode to off or to on
if (
(telemetryStatus && !settings.TELEMETRY_ENABLED) ||
settings.TELEMETRY_ENABLED
) {
analytics.setUserProperties(conf)
}
telemetryStatus = settings.TELEMETRY_ENABLED
analytics.setAnalyticsCollectionEnabled(telemetryStatus)
})
analytics.setAnalyticsCollectionEnabled(telemetryStatus)
}
export function logHoppRequestRunToAnalytics(ev: HoppRequestEvent) {
if (settingsStore.value.TELEMETRY_ENABLED) {
analytics.logEvent("hopp-request", ev)
}
}

View File

@@ -1,11 +1,16 @@
import firebase from "firebase"
import { BehaviorSubject } from "rxjs"
import { BehaviorSubject, Subject } from "rxjs"
export type HoppUser = firebase.User & {
provider?: string
accessToken?: string
}
type AuthEvents =
| { event: "login"; user: HoppUser }
| { event: "logout" }
| { event: "authTokenUpdate"; user: HoppUser; newToken: string | null }
/**
* A BehaviorSubject emitting the currently logged in user (or null if not logged in)
*/
@@ -15,6 +20,11 @@ export const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
*/
export const authIdToken$ = new BehaviorSubject<string | null>(null)
/**
* A subject that emits events related to authentication flows
*/
export const authEvents$ = new Subject<AuthEvents>()
/**
* Initializes the firebase authentication related subjects
*/
@@ -22,6 +32,9 @@ export function initAuth() {
let extraSnapshotStop: (() => void) | null = null
firebase.auth().onAuthStateChanged((user) => {
/** Whether the user was logged in before */
const wasLoggedIn = currentUser$.value !== null
if (!user && extraSnapshotStop) {
extraSnapshotStop()
extraSnapshotStop = null
@@ -61,14 +74,35 @@ export function initAuth() {
userUpdate.provider = data.provider
userUpdate.accessToken = data.accessToken
}
currentUser$.next(userUpdate)
})
}
currentUser$.next(user)
// User wasn't found before, but now is there (login happened)
if (!wasLoggedIn && user) {
authEvents$.next({
event: "login",
user: currentUser$.value!!,
})
} else if (wasLoggedIn && !user) {
// User was found before, but now is not there (logout happened)
authEvents$.next({
event: "logout",
})
}
})
firebase.auth().onIdTokenChanged(async (user) => {
if (user) {
authIdToken$.next(await user.getIdToken())
authEvents$.next({
event: "authTokenUpdate",
newToken: authIdToken$.value,
user: currentUser$.value!!, // Force not-null because user is defined
})
} else {
authIdToken$.next(null)
}

View File

@@ -1,4 +1,5 @@
import firebase from "firebase"
import { initAnalytics } from "./analytics"
import { initAuth } from "./auth"
import { initCollections } from "./collections"
import { initEnvironments } from "./environments"
@@ -13,13 +14,14 @@ const firebaseConfig = {
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID,
appId: process.env.APP_ID,
measurementId: process.env.MEASUREMENT_ID,
measurementId: process.env.FB_MEASUREMENT_ID,
}
let initialized = false
export function initializeFirebase() {
if (!initialized) {
try {
firebase.initializeApp(firebaseConfig)
initAuth()
@@ -27,7 +29,12 @@ export function initializeFirebase() {
initCollections()
initHistory()
initEnvironments()
initAnalytics()
initialized = true
} catch (e) {
// initializeApp throws exception if we reinitialize
initialized = true
}
}
}

View File

@@ -25,5 +25,21 @@ const runAppropriateStrategy = (req) => {
return AxiosStrategy(req)
}
/**
* Returns an identifier for how a request will be ran
* if the system is asked to fire a request
*
* @returns {"normal" | "extension" | "proxy"}
*/
export function getCurrentStrategyID() {
if (isExtensionsAllowed() && hasExtensionInstalled()) {
return "extension"
} else if (settingsStore.value.PROXY_ENABLED) {
return "proxy"
} else {
return "normal"
}
}
export const sendNetworkRequest = (req) =>
runAppropriateStrategy(req).finally(() => window.$nuxt.$loading.finish())

View File

@@ -286,6 +286,8 @@
"are_you_sure_remove_folder": "Are you sure you want to remove this folder?",
"are_you_sure_remove_request": "Are you sure you want to remove this request?",
"are_you_sure_remove_environment": "Are you sure you want to remove this environment?",
"are_you_sure_remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
"telemetry_helps_us": "Telemetry helps us to personalize our operations and deliver the best experience to you.",
"select_next_method": "Select Next method",
"select_previous_method": "Select Previous method",
"select_get_method": "Select GET method",
@@ -345,5 +347,6 @@
"share": "Share",
"interceptor": "Interceptor",
"profile": "Profile",
"are_you_sure_logout": "Are you sure you want to logout?"
"are_you_sure_logout": "Are you sure you want to logout?",
"telemetry": "Telemetry"
}

View File

@@ -66,15 +66,12 @@
<script>
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
import {
setupLocalPersistence,
getLocalConfig,
} from "~/newstore/localpersistence"
import { setupLocalPersistence } from "~/newstore/localpersistence"
import { performMigrations } from "~/helpers/migrations"
import { initUserInfo } from "~/helpers/teams/BackendUserInfo"
import { registerApolloAuthUpdate } from "~/helpers/apollo"
import { initializeFirebase } from "~/helpers/fb"
import { getSettingSubject } from "~/newstore/settings"
export default {
components: { Splitpanes, Pane },
@@ -93,8 +90,13 @@ export default {
beforeMount() {
registerApolloAuthUpdate()
const color = getLocalConfig("THEME_COLOR") || "green"
this.$subscribeTo(getSettingSubject("THEME_COLOR"), (color) => {
document.documentElement.setAttribute("data-accent", color)
})
this.$subscribeTo(getSettingSubject("BG_COLOR"), (color) => {
this.$colorMode.preference = color
})
},
async mounted() {
performMigrations()
@@ -112,13 +114,13 @@ export default {
if (workbox) {
workbox.addEventListener("installed", (event) => {
if (event.isUpdate) {
this.$toast.show(this.$t("new_version_found"), {
this.$toast.show(this.$t("new_version_found").toString(), {
icon: "info",
duration: 0,
theme: "toasted-primary",
action: [
{
text: this.$t("reload"),
text: this.$t("reload").toString(),
onClick: (_, toastObject) => {
toastObject.goAway(0)
window.location.reload()

View File

@@ -3,7 +3,14 @@
import clone from "lodash/clone"
import assign from "lodash/assign"
import eq from "lodash/eq"
import { settingsStore, bulkApplySettings, defaultSettings } from "./settings"
import {
settingsStore,
bulkApplySettings,
defaultSettings,
applySetting,
HoppAccentColor,
HoppBgColor,
} from "./settings"
import {
restHistoryStore,
graphqlHistoryStore,
@@ -55,6 +62,20 @@ function checkAndMigrateOldSettings() {
delete vuexData.postwoman.environments
window.localStorage.setItem("vuex", JSON.stringify(vuexData))
}
if (window.localStorage.getItem("THEME_COLOR")) {
const themeColor = window.localStorage.getItem("THEME_COLOR")
applySetting("THEME_COLOR", themeColor as HoppAccentColor)
window.localStorage.removeItem("THEME_COLOR")
}
if (window.localStorage.getItem("nuxt-color-mode")) {
const color = window.localStorage.getItem("nuxt-color-mode") as HoppBgColor
applySetting("BG_COLOR", color)
window.localStorage.removeItem("BG_COLOR")
}
}
function setupSettingsPersistence() {

View File

@@ -4,7 +4,47 @@ import { Observable } from "rxjs"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import type { KeysMatching } from "~/types/ts-utils"
export const defaultSettings = {
export const HoppBgColors = ["system", "light", "dark", "black"] as const
export type HoppBgColor = typeof HoppBgColors[number]
export const HoppAccentColors = [
"blue",
"green",
"teal",
"indigo",
"purple",
"orange",
"pink",
"red",
"yellow",
] as const
export type HoppAccentColor = typeof HoppAccentColors[number]
export type SettingsType = {
syncCollections: boolean
syncHistory: boolean
syncEnvironments: boolean
SCROLL_INTO_ENABLED: boolean
PROXY_ENABLED: boolean
PROXY_URL: string
PROXY_KEY: string
EXTENSIONS_ENABLED: boolean
EXPERIMENTAL_URL_BAR_ENABLED: boolean
URL_EXCLUDES: {
auth: boolean
httpUser: boolean
httpPassword: boolean
bearerToken: boolean
}
THEME_COLOR: HoppAccentColor
BG_COLOR: HoppBgColor
TELEMETRY_ENABLED: boolean
}
export const defaultSettings: SettingsType = {
syncCollections: true,
syncHistory: true,
syncEnvironments: true,
@@ -21,10 +61,11 @@ export const defaultSettings = {
httpPassword: true,
bearerToken: true,
},
THEME_COLOR: "green",
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
}
export type SettingsType = typeof defaultSettings
const validKeys = Object.keys(defaultSettings)
const dispatchers = defineDispatchers({

View File

@@ -455,9 +455,10 @@ import { Splitpanes, Pane } from "splitpanes"
import * as gql from "graphql"
import { commonHeaders } from "~/helpers/headers"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { sendNetworkRequest } from "~/helpers/network"
import { getCurrentStrategyID, sendNetworkRequest } from "~/helpers/network"
import { getSettingSubject } from "~/newstore/settings"
import { addGraphqlHistoryEntry } from "~/newstore/history"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
export default {
components: { Splitpanes, Pane },
@@ -818,6 +819,11 @@ export default {
})
console.log("Error", error)
}
logHoppRequestRunToAnalytics({
platform: "graphql-query",
strategy: getCurrentStrategyID(),
})
},
// NOTE : schema required here is the GQL Schema document object, not the schema string
@@ -887,6 +893,11 @@ export default {
await this.getSchema()
this.pollSchema()
logHoppRequestRunToAnalytics({
platform: "graphql-schema",
strategy: getCurrentStrategyID(),
})
}
},
async pollSchema() {

View File

@@ -167,6 +167,12 @@
{{ $t("use_experimental_url_bar") }}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle :on="TELEMETRY_ENABLED" @change="showConfirmModal">
{{ $t("telemetry") }}
{{ TELEMETRY_ENABLED ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</div>
</div>
</fieldset>
</div>
@@ -275,8 +281,19 @@
</div>
</div>
</div>
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" />
<FirebaseEmail :show="showEmail" @hide-modal="showEmail = false" />
<SmartConfirmModal
:show="confirmRemove"
:title="`${$t('are_you_sure_remove_telemetry')} ${$t(
'telemetry_helps_us'
)}`"
@hide-modal="confirmRemove = false"
@resolve="
toggleSetting('TELEMETRY_ENABLED')
confirmRemove = false
"
/>
</div>
</template>
@@ -319,6 +336,9 @@ export default Vue.extend({
showLogin: false,
active: getLocalConfig("THEME_COLOR") || "green",
confirmRemove: false,
TELEMETRY_ENABLED: null,
}
},
subscriptions() {
@@ -339,6 +359,8 @@ export default Vue.extend({
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
SYNC_HISTORY: getSettingSubject("syncHistory"),
TELEMETRY_ENABLED: getSettingSubject("TELEMETRY_ENABLED"),
currentUser: currentUser$,
}
},
@@ -365,6 +387,10 @@ export default Vue.extend({
},
},
methods: {
showConfirmModal() {
if (this.TELEMETRY_ENABLED) this.confirmRemove = true
else toggleSetting("TELEMETRY_ENABLED")
},
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
applySetting(key, value)
},

View File

@@ -34,6 +34,11 @@ export const SETTINGS_KEYS = [
*/
"EXTENSIONS_ENABLED",
/**
* A boolean value indicating whether Telemetry is enabled.
*/
"TELEMETRY_ENABLED",
/**
* A boolean value indicating whether to use the URL bar experiments
*/