From 9af8a24a89b6e61b6961debb9cc0ea10d87d28d2 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Sun, 17 Oct 2021 15:03:45 +0530 Subject: [PATCH] feat: initial team invites implementation Co-authored-by: Liyas Thomas --- .../components/teams/Invite.vue | 369 ++++++++---------- .../hoppscotch-app/components/teams/index.vue | 7 +- .../helpers/backend/GQLClient.ts | 226 ++++++----- .../gql/queries/pendingInvites.graphql | 10 + 4 files changed, 306 insertions(+), 306 deletions(-) create mode 100644 packages/hoppscotch-app/helpers/backend/gql/queries/pendingInvites.graphql diff --git a/packages/hoppscotch-app/components/teams/Invite.vue b/packages/hoppscotch-app/components/teams/Invite.vue index 966aa5ee3..4f61d3e53 100644 --- a/packages/hoppscotch-app/components/teams/Invite.vue +++ b/packages/hoppscotch-app/components/teams/Invite.vue @@ -9,53 +9,88 @@
- - -
- -
+
-
- - - {{ $t("empty.pending_invites") }} - +
+
+
+ + +
+ +
+
+
+
+ + + {{ $t("empty.pending_invites") }} + +
+
+ help_outline + {{ $t("error.something_went_wrong") }} +
@@ -66,21 +101,21 @@
@@ -104,9 +139,9 @@ :placeholder="$t('team.permissions')" :name="'value' + index" :value=" - typeof member.value === 'string' - ? member.value - : JSON.stringify(member.value) + typeof invitee.value === 'string' + ? invitee.value + : JSON.stringify(invitee.value) " readonly /> @@ -114,15 +149,15 @@ @@ -133,12 +168,12 @@ :title="$t('action.remove')" svg="trash" color="red" - @click.native="removeTeamMember(index)" + @click.native="removeNewInvitee(index)" />
@@ -162,7 +197,7 @@ - diff --git a/packages/hoppscotch-app/components/teams/index.vue b/packages/hoppscotch-app/components/teams/index.vue index 76132e784..8d041fb47 100644 --- a/packages/hoppscotch-app/components/teams/index.vue +++ b/packages/hoppscotch-app/components/teams/index.vue @@ -49,7 +49,7 @@
help_outline {{ $t("error.something_went_wrong") }} @@ -65,7 +65,6 @@ " :team="myTeams.data.right.myTeams[0]" :show="showModalEdit" - :editing-team="editingTeam" :editingteam-i-d="editingTeamID" @hide-modal="displayModalEdit(false)" @invite-team="inviteTeam(editingTeam, editingTeamID)" @@ -114,7 +113,9 @@ const myTeams = useGQLQuery< MyTeamsQuery, MyTeamsQueryVariables, MyTeamsQueryError ->(MyTeamsDocument) +>({ + query: MyTeamsDocument, +}) watchEffect(() => { console.log(myTeams) diff --git a/packages/hoppscotch-app/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/helpers/backend/GQLClient.ts index eee93e3ee..e1c38cf8e 100644 --- a/packages/hoppscotch-app/helpers/backend/GQLClient.ts +++ b/packages/hoppscotch-app/helpers/backend/GQLClient.ts @@ -1,10 +1,11 @@ import { - computed, ref, - onMounted, - onBeforeUnmount, reactive, Ref, + unref, + watchEffect, + watchSyncEffect, + WatchStopHandle, } from "@nuxtjs/composition-api" import { createClient, @@ -14,6 +15,8 @@ import { OperationContext, fetchExchange, makeOperation, + GraphQLRequest, + createRequest, } from "@urql/core" import { authExchange } from "@urql/exchange-auth" import { offlineExchange } from "@urql/exchange-graphcache" @@ -22,8 +25,7 @@ import { devtoolsExchange } from "@urql/devtools" import * as E from "fp-ts/Either" import * as TE from "fp-ts/TaskEither" import { pipe, constVoid } from "fp-ts/function" -import { subscribe } from "wonka" -import clone from "lodash/clone" +import { Source, subscribe, pipe as wonkaPipe, onEnd } from "wonka" import { keyDefs } from "./caching/keys" import { optimisticDefs } from "./caching/optimistic" import { updatesDef } from "./caching/updates" @@ -45,7 +47,7 @@ const storage = makeDefaultStorage({ maxAge: 7, }) -const client = createClient({ +export const client = createClient({ url: BACKEND_GQL_URL, exchanges: [ devtoolsExchange, @@ -97,6 +99,15 @@ const client = createClient({ ], }) +type MaybeRef = X | Ref + +type UseQueryOptions = { + query: TypedDocumentNode + variables?: MaybeRef + + defer?: boolean +} + /** * A wrapper type for defining errors possible in a GQL operation */ @@ -110,118 +121,125 @@ export type GQLError = error: T } -const DEFAULT_QUERY_OPTIONS = { - noPolling: false, - pause: undefined as Ref | undefined, -} +export const useGQLQuery = ( + _args: UseQueryOptions +) => { + const stops: WatchStopHandle[] = [] -type GQL_QUERY_OPTIONS = typeof DEFAULT_QUERY_OPTIONS + const args = reactive(_args) -type UseQueryLoading = { - loading: true -} + const loading: Ref = ref(true) + const isStale: Ref = ref(true) + const data: Ref, DocType>> = ref() as any -type UseQueryLoaded< - QueryFailType extends string = "", - QueryReturnType = any -> = { - loading: false - data: E.Either, QueryReturnType> -} + const isPaused: Ref = ref(args.defer ?? false) -type UseQueryReturn = - | UseQueryLoading - | UseQueryLoaded + const request: Ref> = ref( + createRequest( + args.query, + unref(args.variables as any) as any + ) + ) as any -export function isLoadedGQLQuery( - x: UseQueryReturn -): x is { - loading: false - data: E.Either, QueryReturnType> -} { - return !x.loading -} + const source: Ref | undefined> = ref() -export function useGQLQuery< - QueryReturnType = any, - QueryVariables extends object = {}, - QueryFailType extends string = "" ->( - query: TypedDocumentNode, - variables?: QueryVariables, - options: Partial = DEFAULT_QUERY_OPTIONS -): - | { loading: false; data: E.Either, QueryReturnType> } - | { loading: true } { - type DataType = E.Either, QueryReturnType> - - const finalOptions = Object.assign(clone(DEFAULT_QUERY_OPTIONS), options) - - const data = ref() - - let subscription: { unsubscribe(): void } | null = null - - onMounted(() => { - const gqlQuery = client.query(query, variables, { - requestPolicy: "cache-and-network", - }) - - const processResult = (result: OperationResult) => - pipe( - // The target - result.data as QueryReturnType | undefined, - // Define what happens if data does not exist (it is an error) - E.fromNullable( - pipe( - // Take the network error value - result.error?.networkError, - // If it null, set the left to the generic error name - E.fromNullable(result.error?.message), - E.match( - // The left case (network error was null) - (gqlErr) => - >{ - type: "gql_error", - error: gqlErr as QueryFailType, - }, - // The right case (it was a GraphQL Error) - (networkErr) => - >{ - type: "network_error", - error: networkErr, - } - ) - ) + stops.push( + watchEffect( + () => { + const newRequest = createRequest( + args.query, + unref(args.variables as any) as any ) - ) - if (finalOptions.noPolling) { - gqlQuery.toPromise().then((result) => { - data.value = processResult(result) - }) - } else { - subscription = pipe( - gqlQuery, - subscribe((result) => { - data.value = processResult(result) - }) + if (request.value.key !== newRequest.key) { + request.value = newRequest + } + }, + { flush: "pre" } + ) + ) + + stops.push( + watchEffect( + () => { + source.value = !isPaused.value + ? client.executeQuery(request.value, { + requestPolicy: "cache-and-network", + }) + : undefined + }, + { flush: "pre" } + ) + ) + + watchSyncEffect((onInvalidate) => { + if (source.value) { + loading.value = true + isStale.value = false + + onInvalidate( + wonkaPipe( + source.value, + onEnd(() => { + loading.value = false + isStale.value = false + }), + subscribe((res) => { + data.value = pipe( + // The target + res.data as DocType | undefined, + // Define what happens if data does not exist (it is an error) + E.fromNullable( + pipe( + // Take the network error value + res.error?.networkError, + // If it null, set the left to the generic error name + E.fromNullable(res.error?.message), + E.match( + // The left case (network error was null) + (gqlErr) => + >{ + type: "gql_error", + error: gqlErr as DocErrorType, + }, + // The right case (it was a GraphQL Error) + (networkErr) => + >{ + type: "network_error", + error: networkErr, + } + ) + ) + ) + ) + + loading.value = false + }) + ).unsubscribe ) } }) - onBeforeUnmount(() => { - subscription?.unsubscribe() + const execute = (updatedVars?: DocVarType) => { + if (updatedVars) { + args.variables = updatedVars as any + } + + isPaused.value = false + } + + const response = reactive({ + loading, + data, + isStale, + execute, }) - return reactive({ - loading: computed(() => !data.value), - data: data!, - }) as - | { - loading: false - data: DataType - } - | { loading: true } + watchEffect(() => { + console.log(JSON.stringify(response)) + }) + + return response } export const runMutation = < diff --git a/packages/hoppscotch-app/helpers/backend/gql/queries/pendingInvites.graphql b/packages/hoppscotch-app/helpers/backend/gql/queries/pendingInvites.graphql new file mode 100644 index 000000000..bbc10779b --- /dev/null +++ b/packages/hoppscotch-app/helpers/backend/gql/queries/pendingInvites.graphql @@ -0,0 +1,10 @@ +query GetPendingInvites($teamID: ID!) { + team(teamID: $teamID) { + id + teamInvitations { + inviteeRole + inviteeEmail + id + } + } +}