Revert "refactor: minor performance improvements on teams related operations (#3348)"
This reverts commit 887dac5285.
This commit is contained in:
@@ -24,6 +24,7 @@ declare module 'vue' {
|
|||||||
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
|
||||||
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
|
||||||
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
|
||||||
|
AppSocial: typeof import('./components/app/Social.vue')['default']
|
||||||
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
|
||||||
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
|
||||||
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
|
||||||
@@ -143,7 +144,6 @@ declare module 'vue' {
|
|||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
|
|||||||
@@ -249,11 +249,12 @@ import { platform } from "~/platform"
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
import { defineActionHandler, invokeAction } from "@helpers/actions"
|
||||||
|
import { workspaceStatus$, updateWorkspaceTeamName } from "~/newstore/workspace"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey } from "~/helpers/platformutils"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -281,11 +282,10 @@ const currentUser = useReadonlyStream(
|
|||||||
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
const selectedTeam = ref<GetMyTeamsQuery["myTeams"][number] | undefined>()
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const workspaceService = useService(WorkspaceService)
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
const workspaceName = computed(() =>
|
const workspaceName = computed(() =>
|
||||||
workspace.value.type === "personal"
|
workspace.value.type === "personal"
|
||||||
@@ -297,18 +297,20 @@ const refetchTeams = () => {
|
|||||||
teamListAdapter.fetchList()
|
teamListAdapter.fetchList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => myTeams.value,
|
() => myTeams.value,
|
||||||
(newTeams) => {
|
(newTeams) => {
|
||||||
const space = workspace.value
|
if (newTeams && workspace.value.type === "team" && workspace.value.teamID) {
|
||||||
|
const team = newTeams.find((team) => team.id === workspace.value.teamID)
|
||||||
if (newTeams && space.type === "team" && space.teamID) {
|
|
||||||
const team = newTeams.find((team) => team.id === space.teamID)
|
|
||||||
if (team) {
|
if (team) {
|
||||||
selectedTeam.value = team
|
selectedTeam.value = team
|
||||||
// Update the workspace name if it's not the same as the updated team name
|
// Update the workspace name if it's not the same as the updated team name
|
||||||
if (team.name !== space.teamName) {
|
if (team.name !== workspace.value.teamName) {
|
||||||
workspaceService.updateWorkspaceTeamName(team.name)
|
updateWorkspaceTeamName(workspace.value, team.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,8 +162,10 @@ import { computed, nextTick, PropType, ref, watch } from "vue"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
@@ -219,6 +221,7 @@ import {
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { createCollectionGists } from "~/helpers/gist"
|
import { createCollectionGists } from "~/helpers/gist"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import {
|
import {
|
||||||
createNewTab,
|
createNewTab,
|
||||||
currentActiveTab,
|
currentActiveTab,
|
||||||
@@ -237,8 +240,6 @@ import {
|
|||||||
} from "~/helpers/collection/collection"
|
} from "~/helpers/collection/collection"
|
||||||
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
import { currentReorderingStatus$ } from "~/newstore/reordering"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -315,8 +316,7 @@ const creatingGistCollection = ref(false)
|
|||||||
const importingMyCollections = ref(false)
|
const importingMyCollections = ref(false)
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const workspaceService = useService(WorkspaceService)
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
const teamListFetched = ref(false)
|
const teamListFetched = ref(false)
|
||||||
@@ -374,18 +374,17 @@ const updateSelectedTeam = (team: SelectedTeam) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
// Used to switch collection type and team when user switch workspace in the global workspace switcher
|
||||||
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
// Check if there is a teamID in the workspace, if yes, switch to team collection and select the team
|
||||||
// If there is no teamID, switch to my environment
|
// If there is no teamID, switch to my environment
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => workspace.value.teamID,
|
||||||
const space = workspace.value
|
|
||||||
|
|
||||||
if (space.type === "personal") return undefined
|
|
||||||
else return space.teamID
|
|
||||||
},
|
|
||||||
(teamID) => {
|
(teamID) => {
|
||||||
if (!teamID) {
|
if (!teamID) {
|
||||||
switchToMyCollections()
|
switchToMyCollections()
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ import {
|
|||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
|
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
||||||
@@ -315,10 +316,10 @@ import { invokeAction } from "~/helpers/actions"
|
|||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { onMounted } from "vue"
|
import { onMounted } from "vue"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
|
|
||||||
type Scope =
|
type Scope =
|
||||||
| {
|
| {
|
||||||
@@ -352,18 +353,21 @@ type EnvironmentType = "my-environments" | "team-environments"
|
|||||||
|
|
||||||
const myEnvironments = useReadonlyStream(environments$, [])
|
const myEnvironments = useReadonlyStream(environments$, [])
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
const workspace = workspaceService.currentWorkspace
|
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
const teamListFetched = ref(false)
|
const teamListFetched = ref(false)
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
workspaceService.changeWorkspace({
|
changeWorkspace({
|
||||||
teamID: team.id,
|
teamID: team.id,
|
||||||
teamName: team.name,
|
teamName: team.name,
|
||||||
type: "team",
|
type: "team",
|
||||||
|
|||||||
@@ -58,15 +58,16 @@ import {
|
|||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
import { pipe } from "fp-ts/function"
|
import { pipe } from "fp-ts/function"
|
||||||
import * as TE from "fp-ts/TaskEither"
|
import * as TE from "fp-ts/TaskEither"
|
||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { deleteEnvironment } from "~/newstore/environments"
|
import { deleteEnvironment } from "~/newstore/environments"
|
||||||
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
|
||||||
import { useToast } from "~/composables/toast"
|
import { useToast } from "~/composables/toast"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -98,8 +99,7 @@ const currentUser = useReadonlyStream(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// TeamList-Adapter
|
// TeamList-Adapter
|
||||||
const workspaceService = useService(WorkspaceService)
|
const teamListAdapter = new TeamListAdapter(true)
|
||||||
const teamListAdapter = workspaceService.acquireTeamListAdapter(null)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
||||||
const teamListFetched = ref(false)
|
const teamListFetched = ref(false)
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
@@ -152,7 +152,11 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
onLoggedIn(() => {
|
||||||
|
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
// Switch to my environments if workspace is personal and to team environments if workspace is team
|
// Switch to my environments if workspace is personal and to team environments if workspace is team
|
||||||
// also resets selected environment if workspace is personal and the previous selected environment was a team environment
|
// also resets selected environment if workspace is personal and the previous selected environment was a team environment
|
||||||
|
|||||||
@@ -216,8 +216,7 @@ import IconClose from "~icons/lucide/x"
|
|||||||
|
|
||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core"
|
||||||
import { useService } from "dioc/vue"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: HoppTestResult | null | undefined
|
modelValue: HoppTestResult | null | undefined
|
||||||
@@ -232,8 +231,7 @@ const testResults = useVModel(props, "modelValue", emit)
|
|||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
const workspace = workspaceService.currentWorkspace
|
|
||||||
|
|
||||||
const showMyEnvironmentDetailsModal = ref(false)
|
const showMyEnvironmentDetailsModal = ref(false)
|
||||||
const showTeamEnvironmentDetailsModal = ref(false)
|
const showTeamEnvironmentDetailsModal = ref(false)
|
||||||
|
|||||||
@@ -69,11 +69,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue"
|
import { computed, ref } from "vue"
|
||||||
|
import { onLoggedIn } from "@composables/auth"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -89,8 +89,7 @@ const showModalInvite = ref(false)
|
|||||||
const editingTeam = ref<any>({}) // TODO: Check this out
|
const editingTeam = ref<any>({}) // TODO: Check this out
|
||||||
const editingTeamID = ref<any>("")
|
const editingTeamID = ref<any>("")
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const adapter = new TeamListAdapter(true)
|
||||||
const adapter = workspaceService.acquireTeamListAdapter(10000)
|
|
||||||
const adapterLoading = useReadonlyStream(adapter.loading$, false)
|
const adapterLoading = useReadonlyStream(adapter.loading$, false)
|
||||||
const adapterError = useReadonlyStream(adapter.error$, null)
|
const adapterError = useReadonlyStream(adapter.error$, null)
|
||||||
const myTeams = useReadonlyStream(adapter.teamList$, [])
|
const myTeams = useReadonlyStream(adapter.teamList$, [])
|
||||||
@@ -99,6 +98,12 @@ const loading = computed(
|
|||||||
() => adapterLoading.value && myTeams.value.length === 0
|
() => adapterLoading.value && myTeams.value.length === 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
try {
|
||||||
|
adapter.initialize()
|
||||||
|
} catch (e) {}
|
||||||
|
})
|
||||||
|
|
||||||
const displayModalAdd = (shouldDisplay: boolean) => {
|
const displayModalAdd = (shouldDisplay: boolean) => {
|
||||||
showModalAdd.value = shouldDisplay
|
showModalAdd.value = shouldDisplay
|
||||||
adapter.fetchList()
|
adapter.fetchList()
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import { useI18n } from "~/composables/i18n"
|
import { useI18n } from "~/composables/i18n"
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
section?: string
|
section?: string
|
||||||
@@ -26,8 +26,7 @@ defineProps<{
|
|||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
const workspace = workspaceService.currentWorkspace
|
|
||||||
|
|
||||||
const teamWorkspaceName = computed(() => {
|
const teamWorkspaceName = computed(() => {
|
||||||
if (workspace.value.type === "team" && workspace.value.teamName) {
|
if (workspace.value.type === "team" && workspace.value.teamName) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="rootEl">
|
<div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
@@ -69,20 +69,20 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
|
import { onLoggedIn } from "~/composables/auth"
|
||||||
import { useReadonlyStream } from "~/composables/stream"
|
import { useReadonlyStream } from "~/composables/stream"
|
||||||
|
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import IconUsers from "~icons/lucide/users"
|
import IconUsers from "~icons/lucide/users"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
import { useLocalState } from "~/newstore/localstate"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { useElementVisibility, useIntervalFn } from "@vueuse/core"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
@@ -94,37 +94,13 @@ const currentUser = useReadonlyStream(
|
|||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
const workspaceService = useService(WorkspaceService)
|
const teamListadapter = new TeamListAdapter(true)
|
||||||
const teamListadapter = workspaceService.acquireTeamListAdapter(null)
|
|
||||||
const myTeams = useReadonlyStream(teamListadapter.teamList$, [])
|
const myTeams = useReadonlyStream(teamListadapter.teamList$, [])
|
||||||
const isTeamListLoading = useReadonlyStream(teamListadapter.loading$, false)
|
const isTeamListLoading = useReadonlyStream(teamListadapter.loading$, false)
|
||||||
const teamListAdapterError = useReadonlyStream(teamListadapter.error$, null)
|
const teamListAdapterError = useReadonlyStream(teamListadapter.error$, null)
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
||||||
const teamListFetched = ref(false)
|
const teamListFetched = ref(false)
|
||||||
|
|
||||||
const rootEl = ref<HTMLElement>()
|
|
||||||
const elVisible = useElementVisibility(rootEl)
|
|
||||||
|
|
||||||
const { pause: pauseListPoll, resume: resumeListPoll } = useIntervalFn(() => {
|
|
||||||
if (teamListadapter.isInitialized) {
|
|
||||||
teamListadapter.fetchList()
|
|
||||||
}
|
|
||||||
}, 10000)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
elVisible,
|
|
||||||
() => {
|
|
||||||
if (elVisible.value) {
|
|
||||||
teamListadapter.fetchList()
|
|
||||||
|
|
||||||
resumeListPoll()
|
|
||||||
} else {
|
|
||||||
pauseListPoll()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(myTeams, (teams) => {
|
watch(myTeams, (teams) => {
|
||||||
if (teams && !teamListFetched.value) {
|
if (teams && !teamListFetched.value) {
|
||||||
teamListFetched.value = true
|
teamListFetched.value = true
|
||||||
@@ -139,7 +115,7 @@ const loading = computed(
|
|||||||
() => isTeamListLoading.value && myTeams.value.length === 0
|
() => isTeamListLoading.value && myTeams.value.length === 0
|
||||||
)
|
)
|
||||||
|
|
||||||
const workspace = workspaceService.currentWorkspace
|
const workspace = useReadonlyStream(workspaceStatus$, { type: "personal" })
|
||||||
|
|
||||||
const isActiveWorkspace = computed(() => (id: string) => {
|
const isActiveWorkspace = computed(() => (id: string) => {
|
||||||
if (workspace.value.type === "personal") return false
|
if (workspace.value.type === "personal") return false
|
||||||
@@ -148,7 +124,7 @@ const isActiveWorkspace = computed(() => (id: string) => {
|
|||||||
|
|
||||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
REMEMBERED_TEAM_ID.value = team.id
|
||||||
workspaceService.changeWorkspace({
|
changeWorkspace({
|
||||||
teamID: team.id,
|
teamID: team.id,
|
||||||
teamName: team.name,
|
teamName: team.name,
|
||||||
type: "team",
|
type: "team",
|
||||||
@@ -157,11 +133,15 @@ const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
|||||||
|
|
||||||
const switchToPersonalWorkspace = () => {
|
const switchToPersonalWorkspace = () => {
|
||||||
REMEMBERED_TEAM_ID.value = undefined
|
REMEMBERED_TEAM_ID.value = undefined
|
||||||
workspaceService.changeWorkspace({
|
changeWorkspace({
|
||||||
type: "personal",
|
type: "personal",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLoggedIn(() => {
|
||||||
|
teamListadapter.initialize()
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => currentUser.value,
|
() => currentUser.value,
|
||||||
(user) => {
|
(user) => {
|
||||||
|
|||||||
@@ -902,16 +902,36 @@ export default class NewTeamCollectionAdapter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getCollectionChildren(
|
/**
|
||||||
collection: TeamCollection
|
* Expands a collection on the tree
|
||||||
): Promise<TeamCollection[]> {
|
*
|
||||||
|
* When a collection is loaded initially in the adapter, children and requests are not loaded (they will be set to null)
|
||||||
|
* Upon expansion those two fields will be populated
|
||||||
|
*
|
||||||
|
* @param {string} collectionID - The ID of the collection to expand
|
||||||
|
*/
|
||||||
|
async expandCollection(collectionID: string): Promise<void> {
|
||||||
|
// TODO: While expanding one collection, block (or queue) the expansion of the other, to avoid race conditions
|
||||||
|
const tree = this.collections$.value
|
||||||
|
|
||||||
|
const collection = findCollInTree(tree, collectionID)
|
||||||
|
|
||||||
|
if (!collection) return
|
||||||
|
|
||||||
|
if (collection.children != null) return
|
||||||
|
|
||||||
const collections: TeamCollection[] = []
|
const collections: TeamCollection[] = []
|
||||||
|
|
||||||
|
this.loadingCollections$.next([
|
||||||
|
...this.loadingCollections$.getValue(),
|
||||||
|
collectionID,
|
||||||
|
])
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const data = await runGQLQuery({
|
const data = await runGQLQuery({
|
||||||
query: GetCollectionChildrenDocument,
|
query: GetCollectionChildrenDocument,
|
||||||
variables: {
|
variables: {
|
||||||
collectionID: collection.id,
|
collectionID,
|
||||||
cursor:
|
cursor:
|
||||||
collections.length > 0
|
collections.length > 0
|
||||||
? collections[collections.length - 1].id
|
? collections[collections.length - 1].id
|
||||||
@@ -920,8 +940,12 @@ export default class NewTeamCollectionAdapter {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (E.isLeft(data)) {
|
if (E.isLeft(data)) {
|
||||||
|
this.loadingCollections$.next(
|
||||||
|
this.loadingCollections$.getValue().filter((x) => x !== collectionID)
|
||||||
|
)
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Child Collection Fetch Error for ${collection.id}: ${data.left}`
|
`Child Collection Fetch Error for ${collectionID}: ${data.left}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,25 +965,23 @@ export default class NewTeamCollectionAdapter {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getCollectionRequests(
|
|
||||||
collection: TeamCollection
|
|
||||||
): Promise<TeamRequest[]> {
|
|
||||||
const requests: TeamRequest[] = []
|
const requests: TeamRequest[] = []
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const data = await runGQLQuery({
|
const data = await runGQLQuery({
|
||||||
query: GetCollectionRequestsDocument,
|
query: GetCollectionRequestsDocument,
|
||||||
variables: {
|
variables: {
|
||||||
collectionID: collection.id,
|
collectionID,
|
||||||
cursor:
|
cursor:
|
||||||
requests.length > 0 ? requests[requests.length - 1].id : undefined,
|
requests.length > 0 ? requests[requests.length - 1].id : undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (E.isLeft(data)) {
|
if (E.isLeft(data)) {
|
||||||
|
this.loadingCollections$.next(
|
||||||
|
this.loadingCollections$.getValue().filter((x) => x !== collectionID)
|
||||||
|
)
|
||||||
|
|
||||||
throw new Error(`Child Request Fetch Error for ${data}: ${data.left}`)
|
throw new Error(`Child Request Fetch Error for ${data}: ${data.left}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,7 +989,7 @@ export default class NewTeamCollectionAdapter {
|
|||||||
...data.right.requestsInCollection.map<TeamRequest>((el) => {
|
...data.right.requestsInCollection.map<TeamRequest>((el) => {
|
||||||
return {
|
return {
|
||||||
id: el.id,
|
id: el.id,
|
||||||
collectionID: collection.id,
|
collectionID,
|
||||||
title: el.title,
|
title: el.title,
|
||||||
request: translateToNewRequest(JSON.parse(el.request)),
|
request: translateToNewRequest(JSON.parse(el.request)),
|
||||||
}
|
}
|
||||||
@@ -978,50 +1000,17 @@ export default class NewTeamCollectionAdapter {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return requests
|
collection.children = collections
|
||||||
}
|
collection.requests = requests
|
||||||
|
|
||||||
/**
|
// Add to the entity ids set
|
||||||
* Expands a collection on the tree
|
collections.forEach((coll) => this.entityIDs.add(`collection-${coll.id}`))
|
||||||
*
|
requests.forEach((req) => this.entityIDs.add(`request-${req.id}`))
|
||||||
* When a collection is loaded initially in the adapter, children and requests are not loaded (they will be set to null)
|
|
||||||
* Upon expansion those two fields will be populated
|
|
||||||
*
|
|
||||||
* @param {string} collectionID - The ID of the collection to expand
|
|
||||||
*/
|
|
||||||
async expandCollection(collectionID: string): Promise<void> {
|
|
||||||
// TODO: While expanding one collection, block (or queue) the expansion of the other, to avoid race conditions
|
|
||||||
const tree = this.collections$.value
|
|
||||||
|
|
||||||
const collection = findCollInTree(tree, collectionID)
|
this.loadingCollections$.next(
|
||||||
|
this.loadingCollections$.getValue().filter((x) => x !== collectionID)
|
||||||
|
)
|
||||||
|
|
||||||
if (!collection) return
|
this.collections$.next(tree)
|
||||||
|
|
||||||
if (collection.children != null) return
|
|
||||||
|
|
||||||
this.loadingCollections$.next([
|
|
||||||
...this.loadingCollections$.getValue(),
|
|
||||||
collectionID,
|
|
||||||
])
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [collections, requests] = await Promise.all([
|
|
||||||
this.getCollectionChildren(collection),
|
|
||||||
this.getCollectionRequests(collection),
|
|
||||||
])
|
|
||||||
|
|
||||||
collection.children = collections
|
|
||||||
collection.requests = requests
|
|
||||||
|
|
||||||
// Add to the entity ids set
|
|
||||||
collections.forEach((coll) => this.entityIDs.add(`collection-${coll.id}`))
|
|
||||||
requests.forEach((req) => this.entityIDs.add(`request-${req.id}`))
|
|
||||||
|
|
||||||
this.collections$.next(tree)
|
|
||||||
} finally {
|
|
||||||
this.loadingCollections$.next(
|
|
||||||
this.loadingCollections$.getValue().filter((x) => x !== collectionID)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,7 @@ export default class TeamListAdapter {
|
|||||||
|
|
||||||
public isInitialized: boolean
|
public isInitialized: boolean
|
||||||
|
|
||||||
constructor(
|
constructor(deferInit = false) {
|
||||||
deferInit = false,
|
|
||||||
private doPolling = true
|
|
||||||
) {
|
|
||||||
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
this.error$ = new BehaviorSubject<GQLError<string> | null>(null)
|
||||||
this.loading$ = new BehaviorSubject<boolean>(false)
|
this.loading$ = new BehaviorSubject<boolean>(false)
|
||||||
this.teamList$ = new BehaviorSubject<GetMyTeamsQuery["myTeams"]>([])
|
this.teamList$ = new BehaviorSubject<GetMyTeamsQuery["myTeams"]>([])
|
||||||
@@ -41,7 +38,7 @@ export default class TeamListAdapter {
|
|||||||
const func = async () => {
|
const func = async () => {
|
||||||
await this.fetchList()
|
await this.fetchList()
|
||||||
|
|
||||||
if (!this.isDispose && this.doPolling) {
|
if (!this.isDispose) {
|
||||||
this.timeoutHandle = setTimeout(() => func(), POLL_DURATION)
|
this.timeoutHandle = setTimeout(() => func(), POLL_DURATION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
packages/hoppscotch-common/src/newstore/workspace.ts
Normal file
67
packages/hoppscotch-common/src/newstore/workspace.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { distinctUntilChanged, pluck } from "rxjs"
|
||||||
|
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
|
||||||
|
|
||||||
|
type Workspace =
|
||||||
|
| { type: "personal" }
|
||||||
|
| { type: "team"; teamID: string; teamName: string }
|
||||||
|
|
||||||
|
type WorkspaceState = {
|
||||||
|
workspace: Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: WorkspaceState = {
|
||||||
|
workspace: {
|
||||||
|
type: "personal",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const dispatchers = defineDispatchers({
|
||||||
|
changeWorkspace(_, { workspace }: { workspace: Workspace }) {
|
||||||
|
return {
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateWorkspaceTeamName(
|
||||||
|
_,
|
||||||
|
{ workspace, newTeamName }: { workspace: Workspace; newTeamName: string }
|
||||||
|
) {
|
||||||
|
if (workspace.type === "team") {
|
||||||
|
return {
|
||||||
|
workspace: {
|
||||||
|
...workspace,
|
||||||
|
teamName: newTeamName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
workspace,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const hoppWorkspaceStore = new DispatchingStore(
|
||||||
|
initialState,
|
||||||
|
dispatchers
|
||||||
|
)
|
||||||
|
|
||||||
|
export const workspaceStatus$ = hoppWorkspaceStore.subject$.pipe(
|
||||||
|
pluck("workspace"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|
||||||
|
export function changeWorkspace(workspace: Workspace) {
|
||||||
|
hoppWorkspaceStore.dispatch({
|
||||||
|
dispatcher: "changeWorkspace",
|
||||||
|
payload: { workspace },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateWorkspaceTeamName(
|
||||||
|
workspace: Workspace,
|
||||||
|
newTeamName: string
|
||||||
|
) {
|
||||||
|
hoppWorkspaceStore.dispatch({
|
||||||
|
dispatcher: "updateWorkspaceTeamName",
|
||||||
|
payload: { workspace, newTeamName },
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
import { describe, expect, vi, it, beforeEach, afterEach } from "vitest"
|
|
||||||
import { TestContainer } from "dioc/testing"
|
|
||||||
import { WorkspaceService } from "../workspace.service"
|
|
||||||
import { setPlatformDef } from "~/platform"
|
|
||||||
import { BehaviorSubject } from "rxjs"
|
|
||||||
import { effectScope, nextTick } from "vue"
|
|
||||||
|
|
||||||
const listAdapterMock = vi.hoisted(() => ({
|
|
||||||
isInitialized: false,
|
|
||||||
initialize: vi.fn(() => {
|
|
||||||
listAdapterMock.isInitialized = true
|
|
||||||
}),
|
|
||||||
dispose: vi.fn(() => {
|
|
||||||
listAdapterMock.isInitialized = false
|
|
||||||
}),
|
|
||||||
fetchList: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
vi.mock("~/helpers/teams/TeamListAdapter", () => ({
|
|
||||||
default: class {
|
|
||||||
isInitialized = listAdapterMock.isInitialized
|
|
||||||
initialize = listAdapterMock.initialize
|
|
||||||
dispose = listAdapterMock.dispose
|
|
||||||
fetchList = listAdapterMock.fetchList
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe("WorkspaceService", () => {
|
|
||||||
const platformMock = {
|
|
||||||
auth: {
|
|
||||||
getCurrentUserStream: vi.fn(),
|
|
||||||
getCurrentUser: vi.fn(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// @ts-expect-error - We're mocking the platform
|
|
||||||
setPlatformDef(platformMock)
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUserStream.mockReturnValue(
|
|
||||||
new BehaviorSubject(null)
|
|
||||||
)
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUser.mockReturnValue(null)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Initialization", () => {
|
|
||||||
it("should initialize with the personal workspace selected", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
expect(service.currentWorkspace.value).toEqual({ type: "personal" })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("updateWorkspaceTeamName", () => {
|
|
||||||
it("should update the workspace team name if the current workspace is a team workspace", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
service.changeWorkspace({
|
|
||||||
type: "team",
|
|
||||||
teamID: "test",
|
|
||||||
teamName: "before update",
|
|
||||||
})
|
|
||||||
|
|
||||||
service.updateWorkspaceTeamName("test")
|
|
||||||
|
|
||||||
expect(service.currentWorkspace.value).toEqual({
|
|
||||||
type: "team",
|
|
||||||
teamID: "test",
|
|
||||||
teamName: "test",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not update the workspace team name if the current workspace is a personal workspace", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
service.changeWorkspace({
|
|
||||||
type: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
service.updateWorkspaceTeamName("test")
|
|
||||||
|
|
||||||
expect(service.currentWorkspace.value).toEqual({ type: "personal" })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("changeWorkspace", () => {
|
|
||||||
it("updates the current workspace value to the given workspace", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
service.changeWorkspace({
|
|
||||||
type: "team",
|
|
||||||
teamID: "test",
|
|
||||||
teamName: "test",
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(service.currentWorkspace.value).toEqual({
|
|
||||||
type: "team",
|
|
||||||
teamID: "test",
|
|
||||||
teamName: "test",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("acquireTeamListAdapter", () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.useFakeTimers()
|
|
||||||
listAdapterMock.fetchList.mockClear()
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
vi.clearAllTimers()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not poll if the polling time is null", () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
listAdapterMock.isInitialized = true // We need to initialize the list adapter before we can use it
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
service.acquireTeamListAdapter(null)
|
|
||||||
vi.advanceTimersByTime(100000)
|
|
||||||
|
|
||||||
expect(listAdapterMock.fetchList).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not poll if the polling time is not null and user not logged in", async () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
service.acquireTeamListAdapter(100)
|
|
||||||
await nextTick()
|
|
||||||
vi.advanceTimersByTime(110)
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUser.mockReturnValue(null)
|
|
||||||
platformMock.auth.getCurrentUserStream.mockReturnValue(
|
|
||||||
new BehaviorSubject(null)
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(listAdapterMock.fetchList).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should poll if the polling time is not null and the user is logged in", async () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
listAdapterMock.isInitialized = true // We need to initialize the list adapter before we can use it
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUser.mockReturnValue({
|
|
||||||
id: "test",
|
|
||||||
})
|
|
||||||
platformMock.auth.getCurrentUserStream.mockReturnValue(
|
|
||||||
new BehaviorSubject({ id: "test" })
|
|
||||||
)
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
const adapter = service.acquireTeamListAdapter(100)
|
|
||||||
await nextTick()
|
|
||||||
vi.advanceTimersByTime(100)
|
|
||||||
|
|
||||||
expect(adapter!.fetchList).toHaveBeenCalledOnce()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("emits 'managed-team-list-adapter-polled' when the service polls the adapter", async () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
listAdapterMock.isInitialized = true
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUser.mockReturnValue({
|
|
||||||
id: "test",
|
|
||||||
})
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUserStream.mockReturnValue(
|
|
||||||
new BehaviorSubject({ id: "test" })
|
|
||||||
)
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
|
|
||||||
const eventFn = vi.fn()
|
|
||||||
const sub = service.getEventStream().subscribe(eventFn)
|
|
||||||
|
|
||||||
service.acquireTeamListAdapter(100)
|
|
||||||
await nextTick()
|
|
||||||
vi.advanceTimersByTime(100)
|
|
||||||
|
|
||||||
expect(eventFn).toHaveBeenCalledOnce()
|
|
||||||
expect(eventFn).toHaveBeenCalledWith({
|
|
||||||
type: "managed-team-list-adapter-polled",
|
|
||||||
})
|
|
||||||
|
|
||||||
sub.unsubscribe()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("stops polling when the Vue effect scope is disposed and there is no more polling locks", async () => {
|
|
||||||
const container = new TestContainer()
|
|
||||||
|
|
||||||
listAdapterMock.isInitialized = true
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUser.mockReturnValue({
|
|
||||||
id: "test",
|
|
||||||
})
|
|
||||||
|
|
||||||
platformMock.auth.getCurrentUserStream.mockReturnValue(
|
|
||||||
new BehaviorSubject({ id: "test" })
|
|
||||||
)
|
|
||||||
|
|
||||||
const service = container.bind(WorkspaceService)
|
|
||||||
listAdapterMock.fetchList.mockClear() // Reset the counters
|
|
||||||
|
|
||||||
const scopeHandle = effectScope()
|
|
||||||
scopeHandle.run(() => {
|
|
||||||
service.acquireTeamListAdapter(100)
|
|
||||||
})
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
vi.advanceTimersByTime(100)
|
|
||||||
|
|
||||||
expect(listAdapterMock.fetchList).toHaveBeenCalledOnce()
|
|
||||||
listAdapterMock.fetchList.mockClear()
|
|
||||||
|
|
||||||
scopeHandle.stop()
|
|
||||||
|
|
||||||
await nextTick()
|
|
||||||
vi.advanceTimersByTime(100)
|
|
||||||
|
|
||||||
expect(listAdapterMock.fetchList).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -25,7 +25,8 @@ import {
|
|||||||
HoppGQLRequest,
|
HoppGQLRequest,
|
||||||
HoppRESTRequest,
|
HoppRESTRequest,
|
||||||
} from "@hoppscotch/data"
|
} from "@hoppscotch/data"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { hoppWorkspaceStore } from "~/newstore/workspace"
|
||||||
|
import { changeWorkspace } from "~/newstore/workspace"
|
||||||
import { invokeAction } from "~/helpers/actions"
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,6 @@ export class CollectionsSpotlightSearcherService
|
|||||||
public searcherSectionTitle = this.t("collection.my_collections")
|
public searcherSectionTitle = this.t("collection.my_collections")
|
||||||
|
|
||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -284,8 +284,8 @@ export class CollectionsSpotlightSearcherService
|
|||||||
const folderPath = path.split("/").map((x) => parseInt(x))
|
const folderPath = path.split("/").map((x) => parseInt(x))
|
||||||
const reqIndex = folderPath.pop()!
|
const reqIndex = folderPath.pop()!
|
||||||
|
|
||||||
if (this.workspaceService.currentWorkspace.value.type !== "personal") {
|
if (hoppWorkspaceStore.value.workspace.type !== "personal") {
|
||||||
this.workspaceService.changeWorkspace({
|
changeWorkspace({
|
||||||
type: "personal",
|
type: "personal",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,16 @@ import { Service } from "dioc"
|
|||||||
import * as E from "fp-ts/Either"
|
import * as E from "fp-ts/Either"
|
||||||
import MiniSearch from "minisearch"
|
import MiniSearch from "minisearch"
|
||||||
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
|
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
|
||||||
|
import { useStreamStatic } from "~/composables/stream"
|
||||||
import { runGQLQuery } from "~/helpers/backend/GQLClient"
|
import { runGQLQuery } from "~/helpers/backend/GQLClient"
|
||||||
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsDocument, GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
import IconEdit from "~icons/lucide/edit"
|
import IconEdit from "~icons/lucide/edit"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import IconUser from "~icons/lucide/user"
|
import IconUser from "~icons/lucide/user"
|
||||||
import IconUserPlus from "~icons/lucide/user-plus"
|
import IconUserPlus from "~icons/lucide/user-plus"
|
||||||
import IconUsers from "~icons/lucide/users"
|
import IconUsers from "~icons/lucide/users"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
|
||||||
|
|
||||||
type Doc = {
|
type Doc = {
|
||||||
text: string | string[]
|
text: string | string[]
|
||||||
@@ -57,9 +58,14 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe
|
|||||||
public searcherSectionTitle = this.t("spotlight.workspace.title")
|
public searcherSectionTitle = this.t("spotlight.workspace.title")
|
||||||
|
|
||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
|
||||||
|
|
||||||
private workspace = this.workspaceService.currentWorkspace
|
private workspace = useStreamStatic(
|
||||||
|
workspaceStatus$,
|
||||||
|
{ type: "personal" },
|
||||||
|
() => {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
)[0]
|
||||||
|
|
||||||
private isTeamSelected = computed(
|
private isTeamSelected = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -164,7 +170,6 @@ export class SwitchWorkspaceSpotlightSearcherService
|
|||||||
public searcherSectionTitle = this.t("workspace.title")
|
public searcherSectionTitle = this.t("workspace.title")
|
||||||
|
|
||||||
private readonly spotlight = this.bind(SpotlightService)
|
private readonly spotlight = this.bind(SpotlightService)
|
||||||
private readonly workspaceService = this.bind(WorkspaceService)
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
@@ -192,7 +197,13 @@ export class SwitchWorkspaceSpotlightSearcherService
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private workspace = this.workspaceService.currentWorkspace
|
private workspace = useStreamStatic(
|
||||||
|
workspaceStatus$,
|
||||||
|
{ type: "personal" },
|
||||||
|
() => {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
)[0]
|
||||||
|
|
||||||
createSearchSession(
|
createSearchSession(
|
||||||
query: Readonly<Ref<string>>
|
query: Readonly<Ref<string>>
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
import { tryOnScopeDispose, useIntervalFn } from "@vueuse/core"
|
|
||||||
import { Service } from "dioc"
|
|
||||||
import { computed, reactive, ref, watch, readonly } from "vue"
|
|
||||||
import { useStreamStatic } from "~/composables/stream"
|
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { min } from "lodash-es"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines a workspace and its information
|
|
||||||
*/
|
|
||||||
export type Workspace =
|
|
||||||
| { type: "personal" }
|
|
||||||
| { type: "team"; teamID: string; teamName: string }
|
|
||||||
|
|
||||||
export type WorkspaceServiceEvent = {
|
|
||||||
type: "managed-team-list-adapter-polled"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This services manages workspace related data and actions in Hoppscotch.
|
|
||||||
*/
|
|
||||||
export class WorkspaceService extends Service<WorkspaceServiceEvent> {
|
|
||||||
public static readonly ID = "WORKSPACE_SERVICE"
|
|
||||||
|
|
||||||
private _currentWorkspace = ref<Workspace>({ type: "personal" })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A readonly reference to the currently selected workspace
|
|
||||||
*/
|
|
||||||
public currentWorkspace = readonly(this._currentWorkspace)
|
|
||||||
|
|
||||||
private teamListAdapterLocks = reactive(new Map<number, number | null>())
|
|
||||||
private teamListAdapterLockTicker = 0 // Used to generate unique lock IDs
|
|
||||||
private managedTeamListAdapter = new TeamListAdapter(true, false)
|
|
||||||
|
|
||||||
private currentUser = useStreamStatic(
|
|
||||||
platform.auth.getCurrentUserStream(),
|
|
||||||
platform.auth.getCurrentUser(),
|
|
||||||
() => {
|
|
||||||
/* noop */
|
|
||||||
}
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
private readonly pollingTime = computed(
|
|
||||||
() =>
|
|
||||||
min(Array.from(this.teamListAdapterLocks.values()).filter((x) => !!x)) ??
|
|
||||||
-1
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
// Dispose the managed team list adapter when the user logs out
|
|
||||||
// and initialize it when the user logs in
|
|
||||||
watch(
|
|
||||||
this.currentUser,
|
|
||||||
(user) => {
|
|
||||||
if (!user && this.managedTeamListAdapter.isInitialized) {
|
|
||||||
this.managedTeamListAdapter.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user && !this.managedTeamListAdapter.isInitialized) {
|
|
||||||
this.managedTeamListAdapter.initialize()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Poll the managed team list adapter if the polling time is defined
|
|
||||||
const { pause: pauseListPoll, resume: resumeListPoll } = useIntervalFn(
|
|
||||||
() => {
|
|
||||||
if (this.managedTeamListAdapter.isInitialized) {
|
|
||||||
this.managedTeamListAdapter.fetchList()
|
|
||||||
|
|
||||||
this.emit({ type: "managed-team-list-adapter-polled" })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
this.pollingTime,
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pause and resume the polling when the polling time changes
|
|
||||||
watch(
|
|
||||||
this.pollingTime,
|
|
||||||
(pollingTime) => {
|
|
||||||
if (pollingTime === -1) {
|
|
||||||
pauseListPoll()
|
|
||||||
} else {
|
|
||||||
resumeListPoll()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Update this function, its existence is pretty weird
|
|
||||||
/**
|
|
||||||
* Updates the name of the current workspace if it is a team workspace.
|
|
||||||
* @param newTeamName The new name of the team
|
|
||||||
*/
|
|
||||||
public updateWorkspaceTeamName(newTeamName: string) {
|
|
||||||
if (this._currentWorkspace.value.type === "team") {
|
|
||||||
this._currentWorkspace.value = {
|
|
||||||
...this._currentWorkspace.value,
|
|
||||||
teamName: newTeamName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the current workspace to the given workspace.
|
|
||||||
* @param workspace The new workspace
|
|
||||||
*/
|
|
||||||
public changeWorkspace(workspace: Workspace) {
|
|
||||||
this._currentWorkspace.value = workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Acquires a team list adapter that is managed by the workspace service.
|
|
||||||
* The team list adapter is associated with a Vue Scope and will be disposed
|
|
||||||
* when the scope is disposed.
|
|
||||||
* @param pollDuration The duration between polls in milliseconds. If null, the team list adapter will not poll.
|
|
||||||
*/
|
|
||||||
public acquireTeamListAdapter(pollDuration: number | null) {
|
|
||||||
const lockID = this.teamListAdapterLockTicker++
|
|
||||||
|
|
||||||
this.teamListAdapterLocks.set(lockID, pollDuration)
|
|
||||||
|
|
||||||
tryOnScopeDispose(() => {
|
|
||||||
this.teamListAdapterLocks.delete(lockID)
|
|
||||||
})
|
|
||||||
|
|
||||||
return this.managedTeamListAdapter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user