diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts
index f2b28b70b..91779e4b5 100644
--- a/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts
+++ b/packages/hoppscotch-backend/src/team-environments/team-environments.service.ts
@@ -194,7 +194,7 @@ export class TeamEnvironmentsService {
const result = await this.prisma.teamEnvironment.create({
data: {
- name: environment.name,
+ name: `${environment.name} - Duplicate`,
teamID: environment.teamID,
variables: environment.variables as Prisma.JsonArray,
},
diff --git a/packages/hoppscotch-common/src/components/environments/Add.vue b/packages/hoppscotch-common/src/components/environments/Add.vue
index 3446d2349..5eed9be63 100644
--- a/packages/hoppscotch-common/src/components/environments/Add.vue
+++ b/packages/hoppscotch-common/src/components/environments/Add.vue
@@ -71,20 +71,21 @@
diff --git a/packages/hoppscotch-common/src/components/environments/Selector.vue b/packages/hoppscotch-common/src/components/environments/Selector.vue
index 1f859bc08..d0e511cd1 100644
--- a/packages/hoppscotch-common/src/components/environments/Selector.vue
+++ b/packages/hoppscotch-common/src/components/environments/Selector.vue
@@ -147,7 +147,7 @@
class="flex flex-col items-center py-4"
>
- {{ getErrorMessage(teamAdapterError) }}
+ {{ t(getEnvActionErrorMessage(teamAdapterError)) }}
@@ -304,10 +304,14 @@ import { TippyComponent } from "vue-tippy"
import { useI18n } from "~/composables/i18n"
import { useReadonlyStream, useStream } from "~/composables/stream"
import { invokeAction } from "~/helpers/actions"
-import { GQLError } from "~/helpers/backend/GQLClient"
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
+import { getEnvActionErrorMessage } from "~/helpers/error-messages"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
+import {
+ sortPersonalEnvironmentsAlphabetically,
+ sortTeamEnvironmentsAlphabetically,
+} from "~/helpers/utils/sortEnvironmentsAlphabetically"
import {
environments$,
globalEnv$,
@@ -316,10 +320,6 @@ import {
} from "~/newstore/environments"
import { useLocalState } from "~/newstore/localstate"
import { WorkspaceService } from "~/services/workspace.service"
-import {
- sortPersonalEnvironmentsAlphabetically,
- sortTeamEnvironmentsAlphabetically,
-} from "~/helpers/utils/sortEnvironmentsAlphabetically"
import IconCheck from "~icons/lucide/check"
import IconEdit from "~icons/lucide/edit"
import IconEye from "~icons/lucide/eye"
@@ -590,18 +590,6 @@ onMounted(() => {
const envSelectorActions = ref(null)
const envQuickPeekActions = ref(null)
-const getErrorMessage = (err: GQLError) => {
- if (err.type === "network_error") {
- return t("error.network_error")
- }
- switch (err.error) {
- case "team_environment/not_found":
- return t("team_environment.not_found")
- default:
- return t("error.something_went_wrong")
- }
-}
-
const globalEnvs = useReadonlyStream(globalEnv$, {} as GlobalEnvironment)
const environmentVariables = computed(() => {
diff --git a/packages/hoppscotch-common/src/components/environments/index.vue b/packages/hoppscotch-common/src/components/environments/index.vue
index 431bbafce..3ff8d2f5a 100644
--- a/packages/hoppscotch-common/src/components/environments/index.vue
+++ b/packages/hoppscotch-common/src/components/environments/index.vue
@@ -7,8 +7,12 @@
@@ -52,16 +56,22 @@ import { Environment, GlobalEnvironment } from "@hoppscotch/data"
import { useService } from "dioc/vue"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
-import { isEqual } from "lodash-es"
+import { cloneDeep, isEqual } from "lodash-es"
import { computed, ref, watch } from "vue"
import { useI18n } from "~/composables/i18n"
import { useToast } from "~/composables/toast"
import { defineActionHandler } from "~/helpers/actions"
import { GQLError } from "~/helpers/backend/GQLClient"
-import { deleteTeamEnvironment } from "~/helpers/backend/mutations/TeamEnvironment"
+import {
+ createTeamEnvironment,
+ deleteTeamEnvironment,
+} from "~/helpers/backend/mutations/TeamEnvironment"
+import { getEnvActionErrorMessage } from "~/helpers/error-messages"
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
import {
+ createEnvironment,
deleteEnvironment,
+ getGlobalVariables,
getSelectedEnvironmentIndex,
globalEnv$,
selectedEnvironmentIndex$,
@@ -88,7 +98,7 @@ const environmentType = ref({
const globalEnv = useReadonlyStream(globalEnv$, {} as GlobalEnvironment)
-const globalEnvironment = computed(() => ({
+const globalEnvironment = computed(() => ({
v: 1 as const,
id: "Global",
name: "Global",
@@ -189,6 +199,7 @@ const editingEnvironmentIndex = ref<"Global" | null>(null)
const editingVariableName = ref("")
const editingVariableValue = ref("")
const secretOptionSelected = ref(false)
+const duplicateGlobalEnvironmentLoading = ref(false)
const position = ref({ top: 0, left: 0 })
@@ -210,6 +221,41 @@ const editEnvironment = (environmentIndex: "Global") => {
displayModalEdit(true)
}
+const duplicateGlobalEnvironment = async () => {
+ if (workspace.value.type === "team") {
+ duplicateGlobalEnvironmentLoading.value = true
+
+ await pipe(
+ createTeamEnvironment(
+ JSON.stringify(globalEnvironment.value.variables),
+ workspace.value.teamID,
+ `Global - ${t("action.duplicate")}`
+ ),
+ TE.match(
+ (err: GQLError) => {
+ console.error(err)
+
+ toast.error(t(getEnvActionErrorMessage(err)))
+ },
+ () => {
+ toast.success(t("environment.duplicated"))
+ }
+ )
+ )()
+
+ duplicateGlobalEnvironmentLoading.value = false
+
+ return
+ }
+
+ createEnvironment(
+ `Global - ${t("action.duplicate")}`,
+ cloneDeep(getGlobalVariables())
+ )
+
+ toast.success(`${t("environment.duplicated")}`)
+}
+
const removeSelectedEnvironment = () => {
const selectedEnvIndex = getSelectedEnvironmentIndex()
if (selectedEnvIndex?.type === "NO_ENV_SELECTED") return
diff --git a/packages/hoppscotch-common/src/components/environments/my/Environment.vue b/packages/hoppscotch-common/src/components/environments/my/Environment.vue
index 5464117d3..f220bf87c 100644
--- a/packages/hoppscotch-common/src/components/environments/my/Environment.vue
+++ b/packages/hoppscotch-common/src/components/environments/my/Environment.vue
@@ -45,7 +45,7 @@
tabindex="0"
role="menu"
@keyup.e="edit!.$el.click()"
- @keyup.d="showDuplicateAction ? duplicate!.$el.click() : null"
+ @keyup.d="duplicate!.$el.click()"
@keyup.j="exportAsJsonEl!.$el.click()"
@keyup.delete="
!(environmentIndex === 'Global')
@@ -59,6 +59,7 @@
:icon="IconEdit"
:label="`${t('action.edit')}`"
:shortcut="['E']"
+ :disabled="duplicateGlobalEnvironmentLoading"
@click="
() => {
emit('edit-environment')
@@ -67,15 +68,15 @@
"
/>
{
duplicateEnvironments()
- hide()
+ !showContextMenuLoadingState && hide()
}
"
/>
@@ -84,6 +85,7 @@
:icon="IconEdit"
:label="`${t('export.as_json')}`"
:shortcut="['J']"
+ :disabled="duplicateGlobalEnvironmentLoading"
@click="
() => {
exportEnvironmentAsJSON()
@@ -97,6 +99,7 @@
:icon="IconTrash2"
:label="`${t('action.delete')}`"
:shortcut="['⌫']"
+ :disabled="duplicateGlobalEnvironmentLoading"
@click="
() => {
confirmRemove = true
@@ -123,17 +126,14 @@ import { useToast } from "@composables/toast"
import { Environment } from "@hoppscotch/data"
import { HoppSmartItem } from "@hoppscotch/ui"
import { useService } from "dioc/vue"
-import { cloneDeep } from "lodash-es"
-import { computed, ref } from "vue"
+import { computed, ref, watch } from "vue"
import { TippyComponent } from "vue-tippy"
import * as E from "fp-ts/Either"
import { exportAsJSON } from "~/helpers/import-export/export/environment"
import {
- createEnvironment,
deleteEnvironment,
duplicateEnvironment,
- getGlobalVariables,
} from "~/newstore/environments"
import { SecretEnvironmentService } from "~/services/secret-environment.service"
import IconCopy from "~icons/lucide/copy"
@@ -148,21 +148,33 @@ const props = withDefaults(
defineProps<{
environment: Environment
environmentIndex: number | "Global" | null
- showDuplicateAction: boolean
+ duplicateGlobalEnvironmentLoading?: boolean
+ showContextMenuLoadingState?: boolean
}>(),
{
- showDuplicateAction: true,
+ duplicateGlobalEnvironmentLoading: false,
+ showContextMenuLoadingState: false,
}
)
const emit = defineEmits<{
(e: "edit-environment"): void
+ (e: "duplicate-global-environment"): void
}>()
const confirmRemove = ref(false)
const secretEnvironmentService = useService(SecretEnvironmentService)
+watch(
+ () => props.duplicateGlobalEnvironmentLoading,
+ (newDuplicateGlobalEnvironmentLoadingVal) => {
+ if (!newDuplicateGlobalEnvironmentLoadingVal) {
+ options?.value?.tippy?.hide()
+ }
+ }
+)
+
const isGlobalEnvironment = computed(() => props.environmentIndex === "Global")
const exportEnvironmentAsJSON = async () => {
@@ -191,14 +203,16 @@ const removeEnvironment = () => {
}
const duplicateEnvironments = () => {
- if (props.environmentIndex === null) return
- if (isGlobalEnvironment.value) {
- createEnvironment(
- `Global - ${t("action.duplicate")}`,
- cloneDeep(getGlobalVariables())
- )
- } else duplicateEnvironment(props.environmentIndex as number)
+ if (props.environmentIndex === null) {
+ return
+ }
+ if (isGlobalEnvironment.value) {
+ emit("duplicate-global-environment")
+ return
+ }
+
+ duplicateEnvironment(props.environmentIndex as number)
toast.success(`${t("environment.duplicated")}`)
}
diff --git a/packages/hoppscotch-common/src/components/environments/teams/Details.vue b/packages/hoppscotch-common/src/components/environments/teams/Details.vue
index 676408beb..3906dced5 100644
--- a/packages/hoppscotch-common/src/components/environments/teams/Details.vue
+++ b/packages/hoppscotch-common/src/components/environments/teams/Details.vue
@@ -165,6 +165,7 @@ import IconHelpCircle from "~icons/lucide/help-circle"
import { platform } from "~/platform"
import { useService } from "dioc/vue"
import { SecretEnvironmentService } from "~/services/secret-environment.service"
+import { getEnvActionErrorMessage } from "~/helpers/error-messages"
type EnvironmentVariable = {
id: number
@@ -405,7 +406,7 @@ const saveEnvironment = async () => {
TE.match(
(err: GQLError) => {
console.error(err)
- toast.error(`${getErrorMessage(err)}`)
+ toast.error(t(getEnvActionErrorMessage(err)))
isLoading.value = false
},
(res) => {
@@ -453,7 +454,7 @@ const saveEnvironment = async () => {
TE.match(
(err: GQLError) => {
console.error(err)
- toast.error(`${getErrorMessage(err)}`)
+ toast.error(t(getEnvActionErrorMessage(err)))
isLoading.value = false
},
() => {
@@ -474,18 +475,4 @@ const hideModal = () => {
selectedEnvOption.value = "variables"
emit("hide-modal")
}
-
-const getErrorMessage = (err: GQLError) => {
- if (err.type === "network_error") {
- return t("error.network_error")
- }
- switch (err.error) {
- case "team_environment/not_found":
- return t("team_environment.not_found")
- case "team_environment/short_name":
- return t("environment.short_name")
- default:
- return t("error.something_went_wrong")
- }
-}
diff --git a/packages/hoppscotch-common/src/components/environments/teams/Environment.vue b/packages/hoppscotch-common/src/components/environments/teams/Environment.vue
index fdc0a683a..a7e74f35c 100644
--- a/packages/hoppscotch-common/src/components/environments/teams/Environment.vue
+++ b/packages/hoppscotch-common/src/components/environments/teams/Environment.vue
@@ -48,6 +48,7 @@
:icon="IconEdit"
:label="`${t('action.edit')}`"
:shortcut="['E']"
+ :disabled="duplicateEnvironmentLoading"
@click="
() => {
emit('edit-environment')
@@ -62,12 +63,8 @@
:icon="IconCopy"
:label="`${t('action.duplicate')}`"
:shortcut="['D']"
- @click="
- () => {
- duplicateEnvironments()
- hide()
- }
- "
+ :loading="duplicateEnvironmentLoading"
+ @click="duplicateEnvironment"
/>
{
exportEnvironmentAsJSON()
@@ -88,6 +86,7 @@
:icon="IconTrash2"
:label="`${t('action.delete')}`"
:shortcut="['⌫']"
+ :disabled="duplicateEnvironmentLoading"
@click="
() => {
confirmRemove = true
@@ -100,6 +99,7 @@
:icon="IconSettings2"
:label="t('action.properties')"
:shortcut="['P']"
+ :disabled="duplicateEnvironmentLoading"
@click="
() => {
emit('show-environment-properties')
@@ -134,8 +134,9 @@ import { useI18n } from "~/composables/i18n"
import { GQLError } from "~/helpers/backend/GQLClient"
import {
deleteTeamEnvironment,
- createDuplicateEnvironment as duplicateEnvironment,
+ createDuplicateEnvironment as duplicateTeamEnvironment,
} from "~/helpers/backend/mutations/TeamEnvironment"
+import { getEnvActionErrorMessage } from "~/helpers/error-messages"
import { exportAsJSON } from "~/helpers/import-export/export/environment"
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
import { SecretEnvironmentService } from "~/services/secret-environment.service"
@@ -177,13 +178,15 @@ const deleteAction = ref()
const exportAsJsonEl = ref()
const propertiesAction = ref()
+const duplicateEnvironmentLoading = ref(false)
+
const removeEnvironment = () => {
pipe(
deleteTeamEnvironment(props.environment.id),
TE.match(
(err: GQLError) => {
console.error(err)
- toast.error(`${getErrorMessage(err)}`)
+ toast.error(t(getEnvActionErrorMessage(err)))
},
() => {
toast.success(`${t("team_environment.deleted")}`)
@@ -193,32 +196,24 @@ const removeEnvironment = () => {
)()
}
-const duplicateEnvironments = () => {
- pipe(
- duplicateEnvironment(props.environment.id),
+const duplicateEnvironment = async () => {
+ duplicateEnvironmentLoading.value = true
+
+ await pipe(
+ duplicateTeamEnvironment(props.environment.id),
TE.match(
(err: GQLError) => {
console.error(err)
- toast.error(`${getErrorMessage(err)}`)
+ toast.error(t(getEnvActionErrorMessage(err)))
},
() => {
toast.success(`${t("environment.duplicated")}`)
}
)
)()
-}
-const getErrorMessage = (err: GQLError) => {
- if (err.type === "network_error") {
- return t("error.network_error")
- }
- switch (err.error) {
- case "team_environment/not_found":
- return t("team_environment.not_found")
- case "team_environment/short_name":
- return t("environment.short_name")
- default:
- return t("error.something_went_wrong")
- }
+ duplicateEnvironmentLoading.value = false
+
+ options.value!.tippy?.hide()
}
diff --git a/packages/hoppscotch-common/src/components/environments/teams/index.vue b/packages/hoppscotch-common/src/components/environments/teams/index.vue
index 6b6e44fcc..a69482bb2 100644
--- a/packages/hoppscotch-common/src/components/environments/teams/index.vue
+++ b/packages/hoppscotch-common/src/components/environments/teams/index.vue
@@ -104,7 +104,7 @@
class="flex flex-col items-center py-4"
>
- {{ getErrorMessage(adapterError) }}
+ {{ t(getEnvActionErrorMessage(adapterError)) }}
{
secretOptionSelected.value = false
}
-const getErrorMessage = (err: GQLError) => {
- if (err.type === "network_error") {
- return t("error.network_error")
- }
- switch (err.error) {
- case "team_environment/not_found":
- return t("team_environment.not_found")
- default:
- return t("error.something_went_wrong")
- }
-}
-
const showEnvironmentProperties = (environmentID: string) => {
showEnvironmentsPropertiesModal.value = true
selectedEnvironmentID.value = environmentID
diff --git a/packages/hoppscotch-common/src/helpers/error-messages/index.ts b/packages/hoppscotch-common/src/helpers/error-messages/index.ts
new file mode 100644
index 000000000..76382204e
--- /dev/null
+++ b/packages/hoppscotch-common/src/helpers/error-messages/index.ts
@@ -0,0 +1,18 @@
+import { GQLError } from "../backend/GQLClient"
+
+export const getEnvActionErrorMessage = (err: GQLError) => {
+ if (err.type === "network_error") {
+ return "error.network_error"
+ }
+
+ switch (err.error) {
+ case "team_environment/not_found":
+ return "team_environment.not_found"
+ case "team_environment/short_name":
+ return "environment.short_name"
+ case "Forbidden resource":
+ return "profile.no_permission"
+ default:
+ return "error.something_went_wrong"
+ }
+}