feat: migrate pre-request script, test script, settings to nuxt composition

This commit is contained in:
liyasthomas
2021-07-21 10:50:20 +05:30
parent d4234f0837
commit 22772ac10f
12 changed files with 333 additions and 218 deletions

View File

@@ -14,7 +14,7 @@
// Text color
--secondary-color: theme("colors.true-gray.400");
// Light Text color
--secondary-light-color: theme("colors.true-gray.200");
--secondary-light-color: theme("colors.true-gray.500");
// Dark Text color
--secondary-dark-color: theme("colors.white");
// Border color

View File

@@ -107,6 +107,7 @@ export default defineComponent({
params,
headers,
preRequestScript: "",
testScript: "",
})
)
} catch (error) {

View File

@@ -0,0 +1,53 @@
<template>
<AppSection label="preRequest">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-110px
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold text-xs">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://github.com/hoppscotch/hoppscotch/wiki/Pre-Request-Scripts"
blank
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="preRequestScript"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '14px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
complete-mode="pre"
/>
</AppSection>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { usePreRequestScript } from "~/newstore/RESTSession"
export default defineComponent({
setup() {
return {
preRequestScript: usePreRequestScript(),
}
},
})
</script>

92
components/http/Tests.vue Normal file
View File

@@ -0,0 +1,92 @@
<template>
<AppSection label="postRequestTests">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-110px
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold text-xs">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://github.com/hoppscotch/hoppscotch/wiki/Post-Request-Tests"
blank
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="testScript"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '14px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
complete-mode="test"
/>
<div v-if="testReports.length !== 0">
<div class="flex flex-1 pl-4 items-center justify-between">
<label class="font-semibold text-xs"> Test Reports </label>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')"
icon="clear_all"
@click.native="clearContent('tests', $event)"
/>
</div>
<div
v-for="(testReport, index) in testReports"
:key="`testReport-${index}`"
class="px-4"
>
<div v-if="testReport.startBlock">
<hr />
<h4 class="heading">
{{ testReport.startBlock }}
</h4>
</div>
<p
v-else-if="testReport.result"
class="flex font-mono flex-1 text-xs info"
>
<span :class="testReport.styles.class" class="flex items-center">
<i class="text-sm material-icons">
{{ testReport.styles.icon }}
</i>
<span>&nbsp;{{ testReport.result }}</span>
<span v-if="testReport.message">
<label>: {{ testReport.message }}</label>
</span>
</span>
</p>
<div v-else-if="testReport.endBlock"><hr /></div>
</div>
</div>
</AppSection>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api"
import { useTestScript } from "~/newstore/RESTSession"
export default defineComponent({
setup() {
return {
testScript: useTestScript(),
testReports: [],
}
},
})
</script>

View File

@@ -20,6 +20,7 @@ export interface HoppRESTRequest {
params: HoppRESTParam[]
headers: HoppRESTHeader[]
preRequestScript: string
testScript: string
}
export function makeRESTRequest(
@@ -56,6 +57,7 @@ export function translateToNewRequest(x: any): HoppRESTRequest {
const method = x.method
const preRequestScript = x.preRequestScript
const testScript = x.testScript
const result: HoppRESTRequest = {
endpoint,
@@ -63,6 +65,7 @@ export function translateToNewRequest(x: any): HoppRESTRequest {
params,
method,
preRequestScript,
testScript,
v: RESTReqSchemaVersion,
}

View File

@@ -0,0 +1,60 @@
import {
customRef,
onBeforeUnmount,
readonly,
Ref,
ref,
} from "@nuxtjs/composition-api"
import { Observable, Subscription } from "rxjs"
export function useReadonlyStream<T>(stream$: Observable<T>, initialValue: T) {
let sub: Subscription | null = null
onBeforeUnmount(() => {
if (sub) {
sub.unsubscribe()
}
})
const targetRef = ref(initialValue) as Ref<T>
sub = stream$.subscribe((value) => {
targetRef.value = value
})
return readonly(targetRef)
}
export function useStream<T>(
stream$: Observable<T>,
initialValue: T,
setter: (val: T) => void
) {
let sub: Subscription | null = null
onBeforeUnmount(() => {
if (sub) {
sub.unsubscribe()
}
})
return customRef((track, trigger) => {
let value = initialValue
sub = stream$.subscribe((val) => {
value = val
trigger()
})
return {
get() {
track()
return value
},
set(value: T) {
trigger()
setter(value)
},
}
})
}

View File

@@ -340,6 +340,7 @@
"we_sent_magic_link_description": "Check your inbox - we sent an email to {email}. It contains a magic link that will log you in.",
"hide_sidebar": "Hide sidebar",
"show_sidebar": "Show sidebar",
"navigation_sidebar": "Navigation sidebar",
"protocols": "Protocols",
"protocol_count": "Protocol {count}",
"share": "Share",

View File

@@ -4,7 +4,7 @@
<Pane class="flex flex-1 overflow-auto">
<Splitpanes vertical :dbl-click-splitter="false">
<Pane
v-if="!hideNavigationPane"
v-if="!HIDE_NAVBAR"
style="width: auto"
class="hide-scrollbar overflow-auto"
>
@@ -28,12 +28,10 @@
<div>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
:title="
hideNavigationPane ? $t('show_sidebar') : $t('hide_sidebar')
"
:title="HIDE_NAVBAR ? $t('show_sidebar') : $t('hide_sidebar')"
icon="menu_open"
:class="{ 'transform rotate-180': hideNavigationPane }"
@click.native="hideNavigationPane = !hideNavigationPane"
:class="{ 'transform rotate-180': HIDE_NAVBAR }"
@click.native="toggleSetting('HIDE_NAVBAR')"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -72,16 +70,21 @@ 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"
import { getSettingSubject, toggleSetting } from "~/newstore/settings"
import { logPageView } from "~/helpers/fb/analytics"
export default {
components: { Splitpanes, Pane },
data() {
return {
hideNavigationPane: false,
zenMode: false,
hideRightPane: false,
HIDE_NAVBAR: null,
}
},
subscriptions() {
return {
HIDE_NAVBAR: getSettingSubject("HIDE_NAVBAR"),
}
},
watch: {
@@ -146,5 +149,10 @@ export default {
beforeDestroy() {
document.removeEventListener("keydown", this._keyListener)
},
methods: {
toggleSetting(key) {
toggleSetting(key)
},
},
}
</script>

View File

@@ -1,6 +1,5 @@
import { pluck, distinctUntilChanged, map, filter } from "rxjs/operators"
import { customRef, onBeforeUnmount, Ref } from "@nuxtjs/composition-api"
import { Subscription } from "rxjs"
import { Ref } from "@nuxtjs/composition-api"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import {
HoppRESTHeader,
@@ -9,6 +8,7 @@ import {
RESTReqSchemaVersion,
} from "~/helpers/types/HoppRESTRequest"
import { HoppRESTResponse } from "~/helpers/types/HoppRESTResponse"
import { useStream } from "~/helpers/utils/composables"
function getParamsInURL(url: string): { key: string; value: string }[] {
const result: { key: string; value: string }[] = []
@@ -124,6 +124,7 @@ const defaultRESTSession: RESTSession = {
headers: [],
method: "GET",
preRequestScript: "// pw.env.set('variable', 'value');",
testScript: "// pw.expect('variable').toBe('value');",
},
response: null,
}
@@ -298,6 +299,14 @@ const dispatchers = defineDispatchers({
},
}
},
setTestScript(curr: RESTSession, { newScript }: { newScript: string }) {
return {
request: {
...curr.request,
testScript: newScript,
},
}
},
updateResponse(
_curr: RESTSession,
{ updatedRes }: { updatedRes: HoppRESTResponse | null }
@@ -416,7 +425,7 @@ export function deleteAllRESTHeaders() {
})
}
export function setPreRequestScript(newScript: string) {
export function setRESTPreRequestScript(newScript: string) {
restSessionStore.dispatch({
dispatcher: "setPreRequestScript",
payload: {
@@ -425,6 +434,15 @@ export function setPreRequestScript(newScript: string) {
})
}
export function setRESTTestScript(newScript: string) {
restSessionStore.dispatch({
dispatcher: "setTestScript",
payload: {
newScript,
},
})
}
export function updateRESTResponse(updatedRes: HoppRESTResponse | null) {
restSessionStore.dispatch({
dispatcher: "updateResponse",
@@ -479,6 +497,11 @@ export const restPreRequestScript$ = restSessionStore.subject$.pipe(
distinctUntilChanged()
)
export const restTestScript$ = restSessionStore.subject$.pipe(
pluck("request", "testScript"),
distinctUntilChanged()
)
export const restResponse$ = restSessionStore.subject$.pipe(
pluck("response"),
distinctUntilChanged()
@@ -499,28 +522,28 @@ export const completedRESTResponse$ = restResponse$.pipe(
* dispatches.
*/
export function usePreRequestScript(): Ref<string> {
let sub: Subscription | null = null
onBeforeUnmount(() => {
if (sub) {
sub.unsubscribe()
return useStream(
restPreRequestScript$,
restSessionStore.value.request.preRequestScript,
(value) => {
setRESTPreRequestScript(value)
}
})
return customRef((track, trigger) => {
sub = restPreRequestScript$.subscribe(() => {
trigger()
})
return {
get() {
track()
return restSessionStore.value.request.preRequestScript
},
set(value: string) {
trigger()
setPreRequestScript(value)
},
}
})
)
}
/**
* A Vue 3 composable function that gives access to a ref
* which is updated to the testScript value in the store.
* The ref value is kept in sync with the store and all writes
* to the ref are dispatched to the store as `setTestScript`
* dispatches.
*/
export function useTestScript(): Ref<string> {
return useStream(
restTestScript$,
restSessionStore.value.request.testScript,
(value) => {
setRESTTestScript(value)
}
)
}

View File

@@ -1,8 +1,10 @@
import { pluck, distinctUntilChanged } from "rxjs/operators"
import has from "lodash/has"
import { Observable } from "rxjs"
import { Ref } from "@nuxtjs/composition-api"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import type { KeysMatching } from "~/types/ts-utils"
import { useStream } from "~/helpers/utils/composables"
export const HoppBgColors = ["system", "light", "dark", "black"] as const
@@ -43,6 +45,7 @@ export type SettingsType = {
BG_COLOR: HoppBgColor
TELEMETRY_ENABLED: boolean
SHORTCUTS_INDICATOR_ENABLED: boolean
HIDE_NAVBAR: boolean
}
export const defaultSettings: SettingsType = {
@@ -66,6 +69,7 @@ export const defaultSettings: SettingsType = {
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
SHORTCUTS_INDICATOR_ENABLED: false,
HIDE_NAVBAR: false,
}
const validKeys = Object.keys(defaultSettings)
@@ -152,3 +156,21 @@ export function applySetting<K extends keyof SettingsType>(
},
})
}
export function useSetting<K extends keyof SettingsType>(
settingKey: K
): Ref<SettingsType[K]> {
return useStream(
settingsStore.subject$.pipe(pluck(settingKey), distinctUntilChanged()),
settingsStore.value[settingKey],
(value: SettingsType[K]) => {
settingsStore.dispatch({
dispatcher: "applySetting",
payload: {
settingKey,
value,
},
})
}
)
}

View File

@@ -31,19 +31,7 @@
<span class="select-wrapper">
<input
id="contentType"
class="
bg-primary
rounded-lg
flex
font-semibold font-mono
text-xs
w-full
py-2
px-4
transition
truncate
focus:outline-none
"
class="bg-primary rounded-lg flex font-semibold font-mono text-xs w-full py-2 px-4 transition truncate focus:outline-none"
v-model="contentType"
readonly
/>
@@ -319,128 +307,11 @@
:id="'pre_request_script'"
:label="$t('pre_request_script')"
>
<AppSection v-if="showPreRequestScript" label="preRequest">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-110px
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold text-xs">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
to="https://github.com/hoppscotch/hoppscotch/wiki/Pre-Request-Scripts"
blank
v-tippy="{ theme: 'tooltip' }"
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="preRequestScript"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '14px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
completeMode="pre"
/>
</AppSection>
<HttpPreRequestScript />
</SmartTab>
<SmartTab :id="'tests'" :label="$t('tests')">
<AppSection v-if="testsEnabled" label="postRequestTests">
<div
class="
bg-primary
border-b border-dividerLight
flex flex-1
pl-4
top-110px
z-10
sticky
items-center
justify-between
"
>
<label class="font-semibold text-xs">
{{ $t("javascript_code") }}
</label>
<ButtonSecondary
to="https://github.com/hoppscotch/hoppscotch/wiki/Post-Request-Tests"
blank
v-tippy="{ theme: 'tooltip' }"
:title="$t('wiki')"
icon="help_outline"
/>
</div>
<SmartJsEditor
v-model="testScript"
:options="{
maxLines: '16',
minLines: '8',
fontSize: '14px',
autoScrollEditorIntoView: true,
showPrintMargin: false,
useWorker: false,
}"
completeMode="test"
/>
<div v-if="testReports.length !== 0">
<div class="flex flex-1 pl-4 items-center justify-between">
<label class="font-semibold text-xs">
Test Reports
</label>
<ButtonSecondary
@click.native="clearContent('tests', $event)"
v-tippy="{ theme: 'tooltip' }"
:title="$t('clear')"
icon="clear_all"
/>
</div>
<div
v-for="(testReport, index) in testReports"
:key="`testReport-${index}`"
class="px-4"
>
<div v-if="testReport.startBlock">
<hr />
<h4 class="heading">
{{ testReport.startBlock }}
</h4>
</div>
<p
v-else-if="testReport.result"
class="flex font-mono flex-1 text-xs info"
>
<span
:class="testReport.styles.class"
class="flex items-center"
>
<i class="text-sm material-icons">
{{ testReport.styles.icon }}
</i>
<span>&nbsp;{{ testReport.result }}</span>
<span v-if="testReport.message">
<label>: {{ testReport.message }}</label>
</span>
</span>
</p>
<div v-else-if="testReport.endBlock"><hr /></div>
</div>
</div>
</AppSection>
<HttpTests />
</SmartTab>
</SmartTabs>
</Pane>
@@ -615,8 +486,6 @@ export default defineComponent({
return {
showCurlImportModal: false,
showPreRequestScript: true,
testsEnabled: true,
testScript: "// pw.expect('variable').toBe('value');",
testReports: [],
copyButton: '<i class="material-icons">content_copy</i>',
downloadButton: '<i class="material-icons">save_alt</i>',
@@ -731,7 +600,6 @@ export default defineComponent({
this.preRequestScript = newValue.preRequestScript
}
if (newValue.testScript) {
this.testsEnabled = true
this.testScript = newValue.testScript
}
this.name = newValue.name
@@ -1333,7 +1201,6 @@ export default defineComponent({
contentType: this.contentType,
requestType: this.requestType,
testScript: this.testScript,
usesPostScripts: this.testsEnabled,
}
if (
@@ -1678,7 +1545,7 @@ export default defineComponent({
requestType: this.requestType,
preRequestScript:
this.showPreRequestScript == true ? this.preRequestScript : null,
testScript: this.testsEnabled == true ? this.testScript : null,
testScript: this.testScript,
name: this.requestName,
}
this.showSaveRequestModal = true

View File

@@ -182,6 +182,15 @@
}}
</SmartToggle>
</div>
<div class="flex items-center">
<SmartToggle
:on="HIDE_NAVBAR"
@change="toggleSetting('HIDE_NAVBAR')"
>
{{ $t("navigation_sidebar") }}
{{ HIDE_NAVBAR ? $t("enabled") : $t("disabled") }}
</SmartToggle>
</div>
</div>
</fieldset>
</div>
@@ -302,21 +311,39 @@
</template>
<script lang="ts">
import Vue from "vue"
import { defineComponent } from "@nuxtjs/composition-api"
import { hasExtensionInstalled } from "../helpers/strategies/ExtensionStrategy"
import {
getSettingSubject,
applySetting,
toggleSetting,
defaultSettings,
useSetting,
} from "~/newstore/settings"
import type { KeysMatching } from "~/types/ts-utils"
import { currentUser$ } from "~/helpers/fb/auth"
import { getLocalConfig } from "~/newstore/localpersistence"
import { useReadonlyStream } from "~/helpers/utils/composables"
type SettingsType = typeof defaultSettings
export default Vue.extend({
export default defineComponent({
setup() {
return {
SCROLL_INTO_ENABLED: useSetting("SCROLL_INTO_ENABLED"),
PROXY_ENABLED: useSetting("PROXY_ENABLED"),
PROXY_URL: useSetting("PROXY_URL"),
PROXY_KEY: useSetting("PROXY_KEY"),
EXTENSIONS_ENABLED: useSetting("EXTENSIONS_ENABLED"),
EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
SYNC_COLLECTIONS: useSetting("syncCollections"),
SYNC_ENVIRONMENTS: useSetting("syncEnvironments"),
SYNC_HISTORY: useSetting("syncHistory"),
TELEMETRY_ENABLED: useSetting("TELEMETRY_ENABLED"),
SHORTCUTS_INDICATOR_ENABLED: useSetting("SHORTCUTS_INDICATOR_ENABLED"),
HIDE_NAVBAR: useSetting("HIDE_NAVBAR"),
currentUser: useReadonlyStream(currentUser$, currentUser$.value),
}
},
data() {
return {
extensionVersion: hasExtensionInstalled()
@@ -325,52 +352,10 @@ export default Vue.extend({
clearIcon: "clear_all",
SYNC_COLLECTIONS: true,
SYNC_ENVIRONMENTS: true,
SYNC_HISTORY: true,
PROXY_URL: "",
PROXY_KEY: "",
EXTENSIONS_ENABLED: true,
PROXY_ENABLED: true,
currentUser: null,
showLogin: false,
active: getLocalConfig("THEME_COLOR") || "green",
confirmRemove: false,
TELEMETRY_ENABLED: null,
SHORTCUTS_INDICATOR_ENABLED: null,
}
},
subscriptions() {
return {
SCROLL_INTO_ENABLED: getSettingSubject("SCROLL_INTO_ENABLED"),
PROXY_ENABLED: getSettingSubject("PROXY_ENABLED"),
PROXY_URL: getSettingSubject("PROXY_URL"),
PROXY_KEY: getSettingSubject("PROXY_KEY"),
EXTENSIONS_ENABLED: getSettingSubject("EXTENSIONS_ENABLED"),
EXPERIMENTAL_URL_BAR_ENABLED: getSettingSubject(
"EXPERIMENTAL_URL_BAR_ENABLED"
),
SYNC_COLLECTIONS: getSettingSubject("syncCollections"),
SYNC_ENVIRONMENTS: getSettingSubject("syncEnvironments"),
SYNC_HISTORY: getSettingSubject("syncHistory"),
TELEMETRY_ENABLED: getSettingSubject("TELEMETRY_ENABLED"),
SHORTCUTS_INDICATOR_ENABLED: getSettingSubject(
"SHORTCUTS_INDICATOR_ENABLED"
),
currentUser: currentUser$,
}
},
head() {
@@ -421,7 +406,7 @@ export default Vue.extend({
resetProxy() {
applySetting("PROXY_URL", `https://proxy.hoppscotch.io/`)
this.clearIcon = "done"
this.$toast.info(this.$t("cleared"), {
this.$toast.info(this.$t("cleared").toString(), {
icon: "clear_all",
})
setTimeout(() => (this.clearIcon = "clear_all"), 1000)