refactor: gql request object and history typing updates

This commit is contained in:
Andrew Bastin
2021-08-24 21:58:04 +05:30
parent c5f8ab3394
commit d1b2539d67
11 changed files with 240 additions and 107 deletions

View File

@@ -97,7 +97,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api" import { defineComponent, PropType } from "@nuxtjs/composition-api"
import { HoppGQLRequest, makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
import { removeGraphqlRequest } from "~/newstore/collections" import { removeGraphqlRequest } from "~/newstore/collections"
import { setGQLSession } from "~/newstore/GQLSession" import { setGQLSession } from "~/newstore/GQLSession"
@@ -107,7 +108,7 @@ export default defineComponent({
picked: { type: Object, default: null }, picked: { type: Object, default: null },
// Whether the request is being saved (activate 'select' event) // Whether the request is being saved (activate 'select' event)
savingMode: { type: Boolean, default: false }, savingMode: { type: Boolean, default: false },
request: { type: Object, default: () => {} }, request: { type: Object as PropType<HoppGQLRequest>, default: () => {} },
folderPath: { type: String, default: null }, folderPath: { type: String, default: null },
requestIndex: { type: Number, default: null }, requestIndex: { type: Number, default: null },
doc: Boolean, doc: Boolean,
@@ -143,11 +144,13 @@ export default defineComponent({
this.pick() this.pick()
} else { } else {
setGQLSession({ setGQLSession({
name: this.$props.request.name, request: makeGQLRequest({
url: this.$props.request.url, name: this.$props.request.name,
query: this.$props.request.query, url: this.$props.request.url,
headers: this.$props.request.headers, query: this.$props.request.query,
variables: this.$props.request.variables, headers: this.$props.request.headers,
variables: this.$props.request.variables,
}),
schema: "", schema: "",
response: "", response: "",
}) })

View File

@@ -291,9 +291,10 @@ import {
} from "~/newstore/GQLSession" } from "~/newstore/GQLSession"
import { commonHeaders } from "~/helpers/headers" import { commonHeaders } from "~/helpers/headers"
import { GQLConnection } from "~/helpers/GQLConnection" import { GQLConnection } from "~/helpers/GQLConnection"
import { addGraphqlHistoryEntry } from "~/newstore/history" import { makeGQLHistoryEntry, addGraphqlHistoryEntry } from "~/newstore/history"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import { getCurrentStrategyID } from "~/helpers/network" import { getCurrentStrategyID } from "~/helpers/network"
import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -377,20 +378,19 @@ export default defineComponent({
response.value = JSON.stringify(JSON.parse(responseText), null, 2) response.value = JSON.stringify(JSON.parse(responseText), null, 2)
const historyEntry = { addGraphqlHistoryEntry(
url: runURL, makeGQLHistoryEntry({
query: runQuery, request: makeGQLRequest({
variables: runVariables, name: "",
star: false, url: runURL,
headers: runHeaders, query: runQuery,
response: response.value, headers: runHeaders,
date: new Date().toLocaleDateString(), variables: runVariables,
time: new Date().toLocaleTimeString(), }),
updatedOn: new Date(), response: response.value,
duration, star: false,
} })
)
addGraphqlHistoryEntry(historyEntry)
$toast.success(t("state.finished_in", { duration }).toString(), { $toast.success(t("state.finished_in", { duration }).toString(), {
icon: "done", icon: "done",

View File

@@ -213,10 +213,10 @@ import {
import { GraphQLField, GraphQLType } from "graphql" import { GraphQLField, GraphQLType } from "graphql"
import { map } from "rxjs/operators" import { map } from "rxjs/operators"
import { GQLConnection } from "~/helpers/GQLConnection" import { GQLConnection } from "~/helpers/GQLConnection"
import { GQLHeader } from "~/helpers/types/HoppGQLRequest"
import { copyToClipboard } from "~/helpers/utils/clipboard" import { copyToClipboard } from "~/helpers/utils/clipboard"
import { useReadonlyStream } from "~/helpers/utils/composables" import { useReadonlyStream } from "~/helpers/utils/composables"
import { import {
GQLHeader,
setGQLHeaders, setGQLHeaders,
setGQLQuery, setGQLQuery,
setGQLResponse, setGQLResponse,

View File

@@ -59,14 +59,19 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, ref } from "@nuxtjs/composition-api" import {
computed,
defineComponent,
PropType,
ref,
} from "@nuxtjs/composition-api"
import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
import { setGQLSession } from "~/newstore/GQLSession" import { setGQLSession } from "~/newstore/GQLSession"
import { GQLHistoryEntry } from "~/newstore/history"
// TODO: Concrete entry data type
export default defineComponent({ export default defineComponent({
props: { props: {
entry: { type: Object, default: () => {} }, entry: { type: Object as PropType<GQLHistoryEntry>, default: () => {} },
showMore: Boolean, showMore: Boolean,
}, },
setup(props) { setup(props) {
@@ -74,8 +79,8 @@ export default defineComponent({
const query = computed(() => const query = computed(() =>
expand.value expand.value
? (props.entry.query.split("\n") as string[]) ? (props.entry.request.query.split("\n") as string[])
: (props.entry.query : (props.entry.request.query
.split("\n") .split("\n")
.slice(0, 2) .slice(0, 2)
.concat(["..."]) as string[]) .concat(["..."]) as string[])
@@ -83,13 +88,15 @@ export default defineComponent({
const useEntry = () => { const useEntry = () => {
setGQLSession({ setGQLSession({
name: "", request: makeGQLRequest({
url: props.entry.url, name: props.entry.request.name,
headers: props.entry.headers, url: props.entry.request.url,
response: props.entry.response, headers: props.entry.request.headers,
query: props.entry.request.query,
variables: props.entry.request.variables,
}),
schema: "", schema: "",
query: props.entry.query, response: props.entry.response,
variables: props.entry.variables,
}) })
} }

View File

@@ -96,6 +96,8 @@ import {
toggleRESTHistoryEntryStar, toggleRESTHistoryEntryStar,
deleteGraphqlHistoryEntry, deleteGraphqlHistoryEntry,
deleteRESTHistoryEntry, deleteRESTHistoryEntry,
RESTHistoryEntry,
GQLHistoryEntry,
} from "~/newstore/history" } from "~/newstore/history"
import { setRESTRequest } from "~/newstore/RESTSession" import { setRESTRequest } from "~/newstore/RESTSession"
@@ -105,7 +107,7 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
return { return {
history: useReadonlyStream( history: useReadonlyStream<RESTHistoryEntry[] | GQLHistoryEntry[]>(
props.page === "rest" ? restHistory$ : graphqlHistory$, props.page === "rest" ? restHistory$ : graphqlHistory$,
[] []
), ),
@@ -120,19 +122,23 @@ export default defineComponent({
}, },
computed: { computed: {
filteredHistory(): any[] { filteredHistory(): any[] {
const filteringHistory = this.history const filteringHistory = this.history as Array<
RESTHistoryEntry | GQLHistoryEntry
>
return filteringHistory.filter((entry) => { return filteringHistory.filter(
const filterText = this.filterText.toLowerCase() (entry: RESTHistoryEntry | GQLHistoryEntry) => {
return Object.keys(entry).some((key) => { const filterText = this.filterText.toLowerCase()
let value = entry[key] return Object.keys(entry).some((key) => {
if (value) { let value = entry[key as keyof typeof entry]
value = typeof value !== "string" ? value.toString() : value if (value) {
return value.toLowerCase().includes(filterText) value = typeof value !== "string" ? value.toString() : value
} return value.toLowerCase().includes(filterText)
return false }
}) return false
}) })
}
)
}, },
}, },
methods: { methods: {

View File

@@ -11,7 +11,7 @@ import {
} from "graphql" } from "graphql"
import { distinctUntilChanged, map } from "rxjs/operators" import { distinctUntilChanged, map } from "rxjs/operators"
import { sendNetworkRequest } from "./network" import { sendNetworkRequest } from "./network"
import { GQLHeader } from "~/newstore/GQLSession" import { GQLHeader } from "./types/HoppGQLRequest"
const GQL_SCHEMA_POLL_INTERVAL = 7000 const GQL_SCHEMA_POLL_INTERVAL = 7000

View File

@@ -3,12 +3,14 @@ import "firebase/firestore"
import { currentUser$ } from "./auth" import { currentUser$ } from "./auth"
import { settingsStore } from "~/newstore/settings" import { settingsStore } from "~/newstore/settings"
import { import {
GQLHistoryEntry,
graphqlHistoryStore, graphqlHistoryStore,
HISTORY_LIMIT, HISTORY_LIMIT,
RESTHistoryEntry, RESTHistoryEntry,
restHistoryStore, restHistoryStore,
setGraphqlHistoryEntries, setGraphqlHistoryEntries,
setRESTHistoryEntries, setRESTHistoryEntries,
translateToNewGQLHistory,
translateToNewRESTHistory, translateToNewRESTHistory,
} from "~/newstore/history" } from "~/newstore/history"
@@ -190,12 +192,12 @@ export function initHistory() {
.orderBy("updatedOn", "desc") .orderBy("updatedOn", "desc")
.limit(HISTORY_LIMIT) .limit(HISTORY_LIMIT)
.onSnapshot((historyRef) => { .onSnapshot((historyRef) => {
const history: any[] = [] const history: GQLHistoryEntry[] = []
historyRef.forEach((doc) => { historyRef.forEach((doc) => {
const entry = doc.data() const entry = doc.data()
entry.id = doc.id entry.id = doc.id
history.push(entry) history.push(translateToNewGQLHistory(entry))
}) })
loadedGraphqlHistory = false loadedGraphqlHistory = false

View File

@@ -0,0 +1,41 @@
export type GQLHeader = {
key: string
value: string
active: boolean
}
export type HoppGQLRequest = {
v: number
name: string
url: string
headers: GQLHeader[]
query: string
variables: string
}
export function translateToGQLRequest(x: any): HoppGQLRequest {
if (x.v && x.v === 1) return x
// Old request
const name = x.name ?? "Untitled"
const url = x.url ?? ""
const headers = x.headers ?? []
const query = x.query ?? ""
const variables = x.variables ?? []
return {
v: 1,
name,
url,
headers,
query,
variables,
}
}
export function makeGQLRequest(x: Omit<HoppGQLRequest, "v">) {
return {
v: 1,
...x,
}
}

View File

@@ -1,35 +1,32 @@
import { distinctUntilChanged, pluck } from "rxjs/operators" import { distinctUntilChanged, pluck } from "rxjs/operators"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore" import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
import { useStream } from "~/helpers/utils/composables" import { useStream } from "~/helpers/utils/composables"
import {
export type GQLHeader = { GQLHeader,
key: string HoppGQLRequest,
value: string makeGQLRequest,
active: boolean } from "~/helpers/types/HoppGQLRequest"
}
type GQLSession = { type GQLSession = {
name: string request: HoppGQLRequest
url: string
headers: GQLHeader[]
schema: string schema: string
query: string
variables: string
response: string response: string
} }
export const defaultGQLSession: GQLSession = { export const defaultGQLSession: GQLSession = {
name: "", request: makeGQLRequest({
url: "https://rickandmortyapi.com/graphql", name: "",
headers: [], url: "https://rickandmortyapi.com/graphql",
schema: "", headers: [],
query: `query GetCharacter($id: ID!) { variables: `{ "id": "1" }`,
query: `query GetCharacter($id: ID!) {
character(id: $id) { character(id: $id) {
id id
name name
} }
}`, }`,
variables: `{ "id": "1" }`, }),
schema: "",
response: "", response: "",
} }
@@ -37,29 +34,44 @@ const dispatchers = defineDispatchers({
setSession(_: GQLSession, { session }: { session: GQLSession }) { setSession(_: GQLSession, { session }: { session: GQLSession }) {
return session return session
}, },
setName(_: GQLSession, { newName }: { newName: string }) { setName(curr: GQLSession, { newName }: { newName: string }) {
return { return {
name: newName, request: {
...curr.request,
name: newName,
},
} }
}, },
setURL(_: GQLSession, { newURL }: { newURL: string }) { setURL(curr: GQLSession, { newURL }: { newURL: string }) {
return { return {
url: newURL, request: {
...curr.request,
url: newURL,
},
} }
}, },
setHeaders(_, { headers }: { headers: GQLHeader[] }) { setHeaders(curr: GQLSession, { headers }: { headers: GQLHeader[] }) {
return { return {
headers, request: {
...curr.request,
headers,
},
} }
}, },
addHeader(curr: GQLSession, { header }: { header: GQLHeader }) { addHeader(curr: GQLSession, { header }: { header: GQLHeader }) {
return { return {
headers: [...curr.headers, header], request: {
...curr.request,
headers: [...curr.request.headers, header],
},
} }
}, },
removeHeader(curr: GQLSession, { headerIndex }: { headerIndex: number }) { removeHeader(curr: GQLSession, { headerIndex }: { headerIndex: number }) {
return { return {
headers: curr.headers.filter((_x, i) => i !== headerIndex), request: {
...curr.request,
headers: curr.request.headers.filter((_x, i) => i !== headerIndex),
},
} }
}, },
updateHeader( updateHeader(
@@ -70,19 +82,28 @@ const dispatchers = defineDispatchers({
}: { headerIndex: number; updatedHeader: GQLHeader } }: { headerIndex: number; updatedHeader: GQLHeader }
) { ) {
return { return {
headers: curr.headers.map((x, i) => request: {
i === headerIndex ? updatedHeader : x ...curr.request,
), headers: curr.request.headers.map((x, i) =>
i === headerIndex ? updatedHeader : x
),
},
} }
}, },
setQuery(_: GQLSession, { newQuery }: { newQuery: string }) { setQuery(curr: GQLSession, { newQuery }: { newQuery: string }) {
return { return {
query: newQuery, request: {
...curr.request,
query: newQuery,
},
} }
}, },
setVariables(_: GQLSession, { newVariables }: { newVariables: string }) { setVariables(curr: GQLSession, { newVariables }: { newVariables: string }) {
return { return {
variables: newVariables, request: {
...curr.request,
variables: newVariables,
},
} }
}, },
setResponse(_: GQLSession, { newResponse }: { newResponse: string }) { setResponse(_: GQLSession, { newResponse }: { newResponse: string }) {
@@ -193,34 +214,32 @@ export function setGQLSession(session: GQLSession) {
} }
export function useGQLRequestName() { export function useGQLRequestName() {
return useStream(gqlName$, gqlSessionStore.value.name, (val) => { return useStream(gqlName$, "", (newName) => {
gqlSessionStore.dispatch({ gqlSessionStore.dispatch({
dispatcher: "setName", dispatcher: "setName",
payload: { payload: { newName },
newName: val,
},
}) })
}) })
} }
export const gqlName$ = gqlSessionStore.subject$.pipe( export const gqlName$ = gqlSessionStore.subject$.pipe(
pluck("name"), pluck("request", "name"),
distinctUntilChanged() distinctUntilChanged()
) )
export const gqlURL$ = gqlSessionStore.subject$.pipe( export const gqlURL$ = gqlSessionStore.subject$.pipe(
pluck("url"), pluck("request", "url"),
distinctUntilChanged() distinctUntilChanged()
) )
export const gqlQuery$ = gqlSessionStore.subject$.pipe( export const gqlQuery$ = gqlSessionStore.subject$.pipe(
pluck("query"), pluck("request", "query"),
distinctUntilChanged() distinctUntilChanged()
) )
export const gqlVariables$ = gqlSessionStore.subject$.pipe( export const gqlVariables$ = gqlSessionStore.subject$.pipe(
pluck("variables"), pluck("request", "variables"),
distinctUntilChanged() distinctUntilChanged()
) )
export const gqlHeaders$ = gqlSessionStore.subject$.pipe( export const gqlHeaders$ = gqlSessionStore.subject$.pipe(
pluck("headers"), pluck("request", "headers"),
distinctUntilChanged() distinctUntilChanged()
) )

View File

@@ -6,6 +6,10 @@ import {
HoppRESTRequest, HoppRESTRequest,
translateToNewRequest, translateToNewRequest,
} from "~/helpers/types/HoppRESTRequest" } from "~/helpers/types/HoppRESTRequest"
import {
HoppGQLRequest,
translateToGQLRequest,
} from "~/helpers/types/HoppGQLRequest"
export type RESTHistoryEntry = { export type RESTHistoryEntry = {
v: number v: number
@@ -22,7 +26,18 @@ export type RESTHistoryEntry = {
id?: string // For when Firebase Firestore is set id?: string // For when Firebase Firestore is set
} }
export function makeHistoryEntry( export type GQLHistoryEntry = {
v: number
request: HoppGQLRequest
response: string
star: boolean
id?: string // For when Firestore ID is set
}
export function makeRESTHistoryEntry(
x: Omit<RESTHistoryEntry, "v"> x: Omit<RESTHistoryEntry, "v">
): RESTHistoryEntry { ): RESTHistoryEntry {
return { return {
@@ -31,6 +46,15 @@ export function makeHistoryEntry(
} }
} }
export function makeGQLHistoryEntry(
x: Omit<GQLHistoryEntry, "v">
): GQLHistoryEntry {
return {
v: 1,
...x,
}
}
export function translateToNewRESTHistory(x: any): RESTHistoryEntry { export function translateToNewRESTHistory(x: any): RESTHistoryEntry {
if (x.v === 1) return x if (x.v === 1) return x
@@ -40,15 +64,33 @@ export function translateToNewRESTHistory(x: any): RESTHistoryEntry {
const duration = x.duration ?? null const duration = x.duration ?? null
const statusCode = x.status ?? null const statusCode = x.status ?? null
const obj: RESTHistoryEntry = { const obj: RESTHistoryEntry = makeRESTHistoryEntry({
v: 1,
request, request,
star, star,
responseMeta: { responseMeta: {
duration, duration,
statusCode, statusCode,
}, },
} })
if (x.id) obj.id = x.id
return obj
}
export function translateToNewGQLHistory(x: any): GQLHistoryEntry {
if (x.v === 1) return x
// Legacy
const request = translateToGQLRequest(x)
const star = x.star ?? false
const response = x.response ?? ""
const obj: GQLHistoryEntry = makeGQLHistoryEntry({
request,
star,
response,
})
if (x.id) obj.id = x.id if (x.id) obj.id = x.id
@@ -60,7 +102,7 @@ export const defaultRESTHistoryState = {
} }
export const defaultGraphqlHistoryState = { export const defaultGraphqlHistoryState = {
state: [] as any[], state: [] as GQLHistoryEntry[],
} }
export const HISTORY_LIMIT = 50 export const HISTORY_LIMIT = 50
@@ -114,17 +156,26 @@ const RESTHistoryDispatchers = defineDispatchers({
}) })
const GQLHistoryDispatchers = defineDispatchers({ const GQLHistoryDispatchers = defineDispatchers({
setEntries(_: GraphqlHistoryType, { entries }: { entries: any[] }) { setEntries(
_: GraphqlHistoryType,
{ entries }: { entries: GQLHistoryEntry[] }
) {
return { return {
state: entries, state: entries,
} }
}, },
addEntry(currentVal: GraphqlHistoryType, { entry }) { addEntry(
currentVal: GraphqlHistoryType,
{ entry }: { entry: GQLHistoryEntry }
) {
return { return {
state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT), state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT),
} }
}, },
deleteEntry(currentVal: GraphqlHistoryType, { entry }) { deleteEntry(
currentVal: GraphqlHistoryType,
{ entry }: { entry: GQLHistoryEntry }
) {
return { return {
state: currentVal.state.filter((e) => !eq(e, entry)), state: currentVal.state.filter((e) => !eq(e, entry)),
} }
@@ -134,7 +185,10 @@ const GQLHistoryDispatchers = defineDispatchers({
state: [], state: [],
} }
}, },
toggleStar(currentVal: GraphqlHistoryType, { entry }) { toggleStar(
currentVal: GraphqlHistoryType,
{ entry }: { entry: GQLHistoryEntry }
) {
return { return {
state: currentVal.state.map((e) => { state: currentVal.state.map((e) => {
if (eq(e, entry) && e.star !== undefined) { if (eq(e, entry) && e.star !== undefined) {
@@ -197,21 +251,21 @@ export function toggleRESTHistoryEntryStar(entry: RESTHistoryEntry) {
}) })
} }
export function setGraphqlHistoryEntries(entries: any[]) { export function setGraphqlHistoryEntries(entries: GQLHistoryEntry[]) {
graphqlHistoryStore.dispatch({ graphqlHistoryStore.dispatch({
dispatcher: "setEntries", dispatcher: "setEntries",
payload: { entries }, payload: { entries },
}) })
} }
export function addGraphqlHistoryEntry(entry: any) { export function addGraphqlHistoryEntry(entry: GQLHistoryEntry) {
graphqlHistoryStore.dispatch({ graphqlHistoryStore.dispatch({
dispatcher: "addEntry", dispatcher: "addEntry",
payload: { entry }, payload: { entry },
}) })
} }
export function deleteGraphqlHistoryEntry(entry: any) { export function deleteGraphqlHistoryEntry(entry: GQLHistoryEntry) {
graphqlHistoryStore.dispatch({ graphqlHistoryStore.dispatch({
dispatcher: "deleteEntry", dispatcher: "deleteEntry",
payload: { entry }, payload: { entry },
@@ -225,7 +279,7 @@ export function clearGraphqlHistory() {
}) })
} }
export function toggleGraphqlHistoryEntryStar(entry: any) { export function toggleGraphqlHistoryEntryStar(entry: GQLHistoryEntry) {
graphqlHistoryStore.dispatch({ graphqlHistoryStore.dispatch({
dispatcher: "toggleStar", dispatcher: "toggleStar",
payload: { entry }, payload: { entry },
@@ -238,7 +292,7 @@ completedRESTResponse$.subscribe((res) => {
if (res.type === "loading" || res.type === "network_fail") return if (res.type === "loading" || res.type === "network_fail") return
addRESTHistoryEntry( addRESTHistoryEntry(
makeHistoryEntry({ makeRESTHistoryEntry({
request: res.req, request: res.req,
responseMeta: { responseMeta: {
duration: res.meta.responseDuration, duration: res.meta.responseDuration,

View File

@@ -17,6 +17,7 @@ import {
setRESTHistoryEntries, setRESTHistoryEntries,
setGraphqlHistoryEntries, setGraphqlHistoryEntries,
translateToNewRESTHistory, translateToNewRESTHistory,
translateToNewGQLHistory,
} from "./history" } from "./history"
import { import {
restCollectionStore, restCollectionStore,
@@ -114,7 +115,7 @@ function setupHistoryPersistence() {
const graphqlHistoryData = JSON.parse( const graphqlHistoryData = JSON.parse(
window.localStorage.getItem("graphqlHistory") || "[]" window.localStorage.getItem("graphqlHistory") || "[]"
) ).map(translateToNewGQLHistory)
setRESTHistoryEntries(restHistoryData) setRESTHistoryEntries(restHistoryData)
setGraphqlHistoryEntries(graphqlHistoryData) setGraphqlHistoryEntries(graphqlHistoryData)