fix: pagination on get my teams fixes #2106

This commit is contained in:
Andrew Bastin
2022-02-03 16:28:55 +05:30
parent ebfbf44140
commit 9918f42bf9
7 changed files with 129 additions and 157 deletions

View File

@@ -46,12 +46,10 @@
</template>
<script setup lang="ts">
import { computed } from "@nuxtjs/composition-api"
import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import { useGQLQuery } from "~/helpers/backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
import { onLoggedIn } from "~/helpers/fb/auth"
import { currentUserInfo$ } from "~/helpers/teams/BackendUserInfo"
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
import { useReadonlyStream } from "~/helpers/utils/composables"
type TeamData = GetMyTeamsQuery["myTeams"][number]
@@ -68,32 +66,16 @@ const emit = defineEmits<{
const currentUser = useReadonlyStream(currentUserInfo$, null)
const teamListQuery = useGQLQuery({
query: GetMyTeamsDocument,
defer: true,
pollDuration: 10000,
})
const adapter = new TeamListAdapter(true)
const myTeams = useReadonlyStream(adapter.teamList$, [])
const myTeams = computed(() => {
const result = teamListQuery.data
if (teamListQuery.loading) {
return []
}
return pipe(
result,
E.match(
// TODO: Handle better error case here ?
() => [],
(x) => x.myTeams
)
)
onLoggedIn(() => {
adapter.initialize()
})
const onTeamSelectIntersect = () => {
// Load team data as soon as intersection
teamListQuery.execute()
adapter.fetchList()
}
const updateCollectionsType = (tabID: string) => {

View File

@@ -6,19 +6,12 @@
outline
@click.native="displayModalAdd(true)"
/>
<div
v-if="myTeams.loading"
class="flex flex-col items-center justify-center"
>
<div v-if="loading" class="flex flex-col items-center justify-center">
<SmartSpinner class="mb-4" />
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
</div>
<div
v-if="
!myTeams.loading &&
E.isRight(myTeams.data) &&
myTeams.data.right.myTeams.length === 0
"
v-if="!loading && myTeams.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
@@ -37,14 +30,14 @@
/>
</div>
<div
v-else-if="!myTeams.loading && E.isRight(myTeams.data)"
v-else-if="!loading"
class="grid gap-4"
:class="
modal ? 'grid-cols-1' : 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'
"
>
<TeamsTeam
v-for="(team, index) in myTeams.data.right.myTeams"
v-for="(team, index) in myTeams"
:key="`team-${String(index)}`"
:team-i-d="team.id"
:team="team"
@@ -53,10 +46,7 @@
@invite-team="inviteTeam(team, team.id)"
/>
</div>
<div
v-if="!myTeams.loading && E.isLeft(myTeams.data)"
class="flex flex-col items-center"
>
<div v-if="!loading && adapterError" class="flex flex-col items-center">
<i class="mb-4 material-icons">help_outline</i>
{{ t("error.something_went_wrong") }}
</div>
@@ -64,12 +54,8 @@
<TeamsAdd :show="showModalAdd" @hide-modal="displayModalAdd(false)" />
<!-- ¯\_()_/¯ -->
<TeamsEdit
v-if="
!myTeams.loading &&
E.isRight(myTeams.data) &&
myTeams.data.right.myTeams.length > 0
"
:team="myTeams.data.right.myTeams[0]"
v-if="!loading && myTeams.length > 0"
:team="myTeams[0]"
:show="showModalEdit"
:editing-team="editingTeam"
:editing-team-i-d="editingTeamID"
@@ -77,12 +63,8 @@
@invite-team="inviteTeam(editingTeam, editingTeamID)"
/>
<TeamsInvite
v-if="
!myTeams.loading &&
E.isRight(myTeams.data) &&
myTeams.data.right.myTeams.length > 0
"
:team="myTeams.data.right.myTeams[0]"
v-if="!loading && myTeams.length > 0"
:team="myTeams[0]"
:show="showModalInvite"
:editing-team="editingTeam"
:editing-team-i-d="editingTeamID"
@@ -92,16 +74,10 @@
</template>
<script setup lang="ts">
import { ref } from "@nuxtjs/composition-api"
import * as E from "fp-ts/Either"
import { useGQLQuery } from "~/helpers/backend/GQLClient"
import {
MyTeamsDocument,
MyTeamsQuery,
MyTeamsQueryVariables,
} from "~/helpers/backend/graphql"
import { MyTeamsQueryError } from "~/helpers/backend/QueryErrors"
import { useI18n } from "~/helpers/utils/composables"
import { computed, ref } from "@nuxtjs/composition-api"
import { onLoggedIn } from "~/helpers/fb/auth"
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
import { useI18n, useReadonlyStream } from "~/helpers/utils/composables"
const t = useI18n()
@@ -115,25 +91,32 @@ const showModalInvite = ref(false)
const editingTeam = ref<any>({}) // TODO: Check this out
const editingTeamID = ref<any>("")
const myTeams = useGQLQuery<
MyTeamsQuery,
MyTeamsQueryVariables,
MyTeamsQueryError
>({
query: MyTeamsDocument,
pollDuration: 5000,
const adapter = new TeamListAdapter(true)
const adapterLoading = useReadonlyStream(adapter.loading$, false)
const adapterError = useReadonlyStream(adapter.error$, null)
const myTeams = useReadonlyStream(adapter.teamList$, [])
const loading = computed(
() => adapterLoading.value && myTeams.value.length === 0
)
onLoggedIn(() => {
adapter.initialize()
})
const displayModalAdd = (shouldDisplay: boolean) => {
showModalAdd.value = shouldDisplay
adapter.fetchList()
}
const displayModalEdit = (shouldDisplay: boolean) => {
showModalEdit.value = shouldDisplay
adapter.fetchList()
}
const displayModalInvite = (shouldDisplay: boolean) => {
showModalInvite.value = shouldDisplay
adapter.fetchList()
}
const editTeam = (team: any, teamID: any) => {

View File

@@ -167,7 +167,9 @@ export const runGQLQuery = <DocType, DocVarType, DocErrorType extends string>(
args: RunQueryOptions<DocType, DocVarType>
): Promise<E.Either<GQLError<DocErrorType>, DocType>> => {
const request = createRequest<DocType, DocVarType>(args.query, args.variables)
const source = client.value.executeQuery(request)
const source = client.value.executeQuery(request, {
requestPolicy: "network-only",
})
return new Promise((resolve) => {
const sub = wonkaPipe(

View File

@@ -1,5 +1,5 @@
import { gql } from "@urql/core"
import { GraphCacheUpdaters, MyTeamsDocument } from "../graphql"
import { GraphCacheUpdaters } from "../graphql"
export const updatesDef: GraphCacheUpdaters = {
Subscription: {
@@ -58,71 +58,6 @@ export const updatesDef: GraphCacheUpdaters = {
},
},
Mutation: {
deleteTeam: (_r, { teamID }, cache, _info) => {
cache.updateQuery(
{
query: MyTeamsDocument,
},
(data) => {
if (data) {
data.myTeams = data.myTeams.filter((x) => x.id !== teamID)
}
return data
}
)
cache.invalidate({
__typename: "Team",
id: teamID,
})
},
leaveTeam: (_r, { teamID }, cache, _info) => {
cache.updateQuery(
{
query: MyTeamsDocument,
},
(data) => {
if (data) {
data.myTeams = data.myTeams.filter((x) => x.id !== teamID)
}
return data
}
)
cache.invalidate({
__typename: "Team",
id: teamID,
})
},
createTeam: (result, _args, cache, _info) => {
cache.updateQuery(
{
query: MyTeamsDocument,
},
(data) => {
if (data) data.myTeams.push(result.createTeam as any)
return data
}
)
},
removeTeamMember: (_result, { teamID, userUid }, cache) => {
const newMembers = (
(cache.resolve(
{
__typename: "Team",
id: teamID,
},
"teamMembers"
) as string[]) ?? []
)
.map((x) => [x, cache.resolve(x, "user") as string])
.map(([key, userKey]) => [key, cache.resolve(userKey, "uid") as string])
.filter(([_key, uid]) => uid !== userUid)
.map(([key]) => key)
cache.link({ __typename: "Team", id: teamID }, "teamMembers", newMembers)
},
createTeamInvitation: (result, _args, cache, _info) => {
cache.invalidate(
{

View File

@@ -1,7 +1,18 @@
query GetMyTeams {
myTeams {
query GetMyTeams($cursor: ID) {
myTeams(cursor: $cursor) {
id
name
myRole
ownersCount
teamMembers {
membershipID
user {
photoURL
displayName
email
uid
}
role
}
}
}

View File

@@ -1,18 +0,0 @@
query MyTeams {
myTeams {
id
name
myRole
ownersCount
teamMembers {
membershipID
user {
photoURL
displayName
email
uid
}
role
}
}
}

View File

@@ -0,0 +1,77 @@
import * as E from "fp-ts/Either"
import { BehaviorSubject } from "rxjs"
import { GQLError, runGQLQuery } from "../backend/GQLClient"
import { GetMyTeamsDocument, GetMyTeamsQuery } from "../backend/graphql"
const BACKEND_PAGE_SIZE = 10
const POLL_DURATION = 10000
export default class TeamListAdapter {
error$: BehaviorSubject<GQLError<string> | null>
loading$: BehaviorSubject<boolean>
teamList$: BehaviorSubject<GetMyTeamsQuery["myTeams"]>
private timeoutHandle: ReturnType<typeof setTimeout> | null
private isDispose: boolean
constructor(deferInit: boolean = false) {
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
this.loading$ = new BehaviorSubject<boolean>(false)
this.teamList$ = new BehaviorSubject<GetMyTeamsQuery["myTeams"]>([])
this.timeoutHandle = null
this.isDispose = false
if (!deferInit) this.initialize()
}
initialize() {
if (this.timeoutHandle) throw new Error(`Adapter already initialized`)
if (this.isDispose) throw new Error(`Adapter has been disposed`)
const func = async () => {
await this.fetchList()
if (!this.isDispose) {
this.timeoutHandle = setTimeout(() => func(), POLL_DURATION)
}
}
func()
}
public dispose() {
this.isDispose = true
clearTimeout(this.timeoutHandle as any)
this.timeoutHandle = null
}
async fetchList() {
this.loading$.next(true)
const results: GetMyTeamsQuery["myTeams"] = []
while (true) {
const result = await runGQLQuery({
query: GetMyTeamsDocument,
variables: {
cursor:
results.length > 0 ? results[results.length - 1].id : undefined,
},
})
if (E.isLeft(result)) {
this.error$.next(result.left)
throw new Error(`Failed fetching teams list: ${result.left}`)
}
results.push(...result.right.myTeams)
// If we don't have full elements in the list, then the list is done usually, so lets stop
if (result.right.myTeams.length !== BACKEND_PAGE_SIZE) break
}
this.teamList$.next(results)
this.loading$.next(false)
}
}