feat: sort environments alphabetically (#4280)
* feat: sort environments alphabetically in sidebar and selector * fix: correct typo in i18n string key * fix: rename and export team environments bug * chore: added sortEnvironments util function * chore: ads doc * chore: cleanup --------- Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -309,6 +309,7 @@
|
|||||||
"select": "Select environment",
|
"select": "Select environment",
|
||||||
"set": "Set environment",
|
"set": "Set environment",
|
||||||
"set_as_environment": "Set as environment",
|
"set_as_environment": "Set as environment",
|
||||||
|
"short_name": "Environment needs to have minimum 3 characters",
|
||||||
"team_environments": "Workspace Environments",
|
"team_environments": "Workspace Environments",
|
||||||
"title": "Environments",
|
"title": "Environments",
|
||||||
"updated": "Environment updated",
|
"updated": "Environment updated",
|
||||||
|
|||||||
@@ -77,24 +77,27 @@
|
|||||||
:label="`${t('environment.my_environments')}`"
|
:label="`${t('environment.my_environments')}`"
|
||||||
>
|
>
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="(gen, index) in myEnvironments"
|
v-for="{
|
||||||
|
env,
|
||||||
|
index,
|
||||||
|
} in alphabeticallySortedPersonalEnvironments"
|
||||||
:key="`gen-${index}`"
|
:key="`gen-${index}`"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="gen.name"
|
:label="env.name"
|
||||||
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
||||||
:active-info-icon="isEnvActive(index)"
|
:active-info-icon="isEnvActive(index)"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleEnvironmentChange(index, {
|
handleEnvironmentChange(index, {
|
||||||
type: 'my-environment',
|
type: 'my-environment',
|
||||||
environment: gen,
|
environment: env,
|
||||||
})
|
})
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="myEnvironments.length === 0"
|
v-if="alphabeticallySortedPersonalEnvironments.length === 0"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
@@ -116,24 +119,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="isTeamSelected" class="flex flex-col">
|
<div v-if="isTeamSelected" class="flex flex-col">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="(gen, index) in teamEnvironmentList"
|
v-for="{ env, index } in alphabeticallySortedTeamEnvironments"
|
||||||
:key="`gen-team-${index}`"
|
:key="`gen-team-${index}`"
|
||||||
:icon="IconLayers"
|
:icon="IconLayers"
|
||||||
:label="gen.environment.name"
|
:label="env.environment.name"
|
||||||
:info-icon="isEnvActive(gen.id) ? IconCheck : undefined"
|
:info-icon="isEnvActive(env.id) ? IconCheck : undefined"
|
||||||
:active-info-icon="isEnvActive(gen.id)"
|
:active-info-icon="isEnvActive(env.id)"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleEnvironmentChange(index, {
|
handleEnvironmentChange(index, {
|
||||||
type: 'team-environment',
|
type: 'team-environment',
|
||||||
environment: gen,
|
environment: env,
|
||||||
})
|
})
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="teamEnvironmentList.length === 0"
|
v-if="alphabeticallySortedTeamEnvironments.length === 0"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
@@ -316,6 +319,10 @@ import { useLocalState } from "~/newstore/localstate"
|
|||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { WorkspaceService } from "~/services/workspace.service"
|
import { WorkspaceService } from "~/services/workspace.service"
|
||||||
|
import {
|
||||||
|
sortPersonalEnvironmentsAlphabetically,
|
||||||
|
sortTeamEnvironmentsAlphabetically,
|
||||||
|
} from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||||
|
|
||||||
type Scope =
|
type Scope =
|
||||||
| {
|
| {
|
||||||
@@ -389,6 +396,15 @@ const teamEnvironmentList = useReadonlyStream(
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Sort environments alphabetically by default
|
||||||
|
const alphabeticallySortedPersonalEnvironments = computed(() =>
|
||||||
|
sortPersonalEnvironmentsAlphabetically(myEnvironments.value, "asc")
|
||||||
|
)
|
||||||
|
|
||||||
|
const alphabeticallySortedTeamEnvironments = computed(() =>
|
||||||
|
sortTeamEnvironmentsAlphabetically(teamEnvironmentList.value, "asc")
|
||||||
|
)
|
||||||
|
|
||||||
const handleEnvironmentChange = (
|
const handleEnvironmentChange = (
|
||||||
index: number,
|
index: number,
|
||||||
env?:
|
env?:
|
||||||
|
|||||||
@@ -386,6 +386,11 @@ const saveEnvironment = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editingName.value.length < 3) {
|
||||||
|
toast.error(`${t("environment.short_name")}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const filteredVariables = pipe(
|
const filteredVariables = pipe(
|
||||||
vars.value,
|
vars.value,
|
||||||
A.filterMap(
|
A.filterMap(
|
||||||
|
|||||||
@@ -26,14 +26,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EnvironmentsMyEnvironment
|
<EnvironmentsMyEnvironment
|
||||||
v-for="(environment, index) in environments"
|
v-for="{ env, index } in alphabeticallySortedPersonalEnvironments"
|
||||||
:key="`environment-${index}`"
|
:key="`environment-${index}`"
|
||||||
:environment-index="index"
|
:environment-index="index"
|
||||||
:environment="environment"
|
:environment="env"
|
||||||
@edit-environment="editEnvironment(index)"
|
@edit-environment="editEnvironment(index)"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!environments.length"
|
v-if="!alphabeticallySortedPersonalEnvironments.length"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref, computed } from "vue"
|
||||||
import { environments$ } from "~/newstore/environments"
|
import { environments$ } from "~/newstore/environments"
|
||||||
import { useColorMode } from "~/composables/theming"
|
import { useColorMode } from "~/composables/theming"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
@@ -87,14 +87,19 @@ import { useI18n } from "~/composables/i18n"
|
|||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import IconImport from "~icons/lucide/folder-down"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||||
import { Environment } from "@hoppscotch/data"
|
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
|
import { sortPersonalEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
|
|
||||||
const environments = useReadonlyStream(environments$, [])
|
const environments = useReadonlyStream(environments$, [])
|
||||||
|
|
||||||
|
// Sort environments alphabetically by default
|
||||||
|
const alphabeticallySortedPersonalEnvironments = computed(() =>
|
||||||
|
sortPersonalEnvironmentsAlphabetically(environments.value, "asc")
|
||||||
|
)
|
||||||
|
|
||||||
const showModalImportExport = ref(false)
|
const showModalImportExport = ref(false)
|
||||||
const showModalDetails = ref(false)
|
const showModalDetails = ref(false)
|
||||||
const action = ref<"new" | "edit">("edit")
|
const action = ref<"new" | "edit">("edit")
|
||||||
@@ -130,11 +135,10 @@ defineActionHandler(
|
|||||||
"modals.my.environment.edit",
|
"modals.my.environment.edit",
|
||||||
({ envName, variableName, isSecret }) => {
|
({ envName, variableName, isSecret }) => {
|
||||||
if (variableName) editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const envIndex: number = environments.value.findIndex(
|
const envIndex: number =
|
||||||
(environment: Environment) => {
|
alphabeticallySortedPersonalEnvironments.value.findIndex(({ env }) => {
|
||||||
return environment.name === envName
|
return env.name === envName
|
||||||
}
|
})
|
||||||
)
|
|
||||||
if (envName !== "Global") {
|
if (envName !== "Global") {
|
||||||
editEnvironment(envIndex)
|
editEnvironment(envIndex)
|
||||||
secretOptionSelected.value = isSecret ?? false
|
secretOptionSelected.value = isSecret ?? false
|
||||||
|
|||||||
@@ -480,6 +480,8 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
switch (err.error) {
|
switch (err.error) {
|
||||||
case "team_environment/not_found":
|
case "team_environment/not_found":
|
||||||
return t("team_environment.not_found")
|
return t("team_environment.not_found")
|
||||||
|
case "team_environment/short_name":
|
||||||
|
return t("environment.short_name")
|
||||||
default:
|
default:
|
||||||
return t("error.something_went_wrong")
|
return t("error.something_went_wrong")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="group flex items-stretch"
|
class="group flex items-stretch"
|
||||||
@contextmenu.prevent="options!.tippy.show()"
|
@contextmenu.prevent="options!.tippy?.show()"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="flex cursor-pointer items-center justify-center px-4"
|
class="flex cursor-pointer items-center justify-center px-4"
|
||||||
@@ -212,6 +212,8 @@ const getErrorMessage = (err: GQLError<string>) => {
|
|||||||
switch (err.error) {
|
switch (err.error) {
|
||||||
case "team_environment/not_found":
|
case "team_environment/not_found":
|
||||||
return t("team_environment.not_found")
|
return t("team_environment.not_found")
|
||||||
|
case "team_environment/short_name":
|
||||||
|
return t("environment.short_name")
|
||||||
default:
|
default:
|
||||||
return t("error.something_went_wrong")
|
return t("error.something_went_wrong")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="!loading && !teamEnvironments.length && !adapterError"
|
v-if="
|
||||||
|
!loading &&
|
||||||
|
!alphabeticallySortedTeamEnvironments.length &&
|
||||||
|
!adapterError
|
||||||
|
"
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||||
:alt="`${t('empty.environments')}`"
|
:alt="`${t('empty.environments')}`"
|
||||||
:text="t('empty.environments')"
|
:text="t('empty.environments')"
|
||||||
@@ -79,15 +83,15 @@
|
|||||||
</HoppSmartPlaceholder>
|
</HoppSmartPlaceholder>
|
||||||
<div v-else-if="!loading">
|
<div v-else-if="!loading">
|
||||||
<EnvironmentsTeamsEnvironment
|
<EnvironmentsTeamsEnvironment
|
||||||
v-for="(environment, index) in JSON.parse(
|
v-for="{ env, index } in JSON.parse(
|
||||||
JSON.stringify(teamEnvironments)
|
JSON.stringify(alphabeticallySortedTeamEnvironments)
|
||||||
)"
|
)"
|
||||||
:key="`environment-${index}`"
|
:key="`environment-${index}`"
|
||||||
:environment="environment"
|
:environment="env"
|
||||||
:is-viewer="team?.role === 'VIEWER'"
|
:is-viewer="team?.role === 'VIEWER'"
|
||||||
@edit-environment="editEnvironment(environment)"
|
@edit-environment="editEnvironment(env)"
|
||||||
@show-environment-properties="
|
@show-environment-properties="
|
||||||
showEnvironmentProperties(environment.environment.id)
|
showEnvironmentProperties(env.environment.id)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +118,9 @@
|
|||||||
/>
|
/>
|
||||||
<EnvironmentsImportExport
|
<EnvironmentsImportExport
|
||||||
v-if="showModalImportExport"
|
v-if="showModalImportExport"
|
||||||
:team-environments="teamEnvironments"
|
:team-environments="
|
||||||
|
alphabeticallySortedTeamEnvironments.map(({ env }) => env)
|
||||||
|
"
|
||||||
:team-id="team?.teamID"
|
:team-id="team?.teamID"
|
||||||
environment-type="TEAM_ENV"
|
environment-type="TEAM_ENV"
|
||||||
@hide-modal="displayModalImportExport(false)"
|
@hide-modal="displayModalImportExport(false)"
|
||||||
@@ -139,6 +145,7 @@ import IconHelpCircle from "~icons/lucide/help-circle"
|
|||||||
import IconImport from "~icons/lucide/folder-down"
|
import IconImport from "~icons/lucide/folder-down"
|
||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { TeamWorkspace } from "~/services/workspace.service"
|
import { TeamWorkspace } from "~/services/workspace.service"
|
||||||
|
import { sortTeamEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -151,6 +158,12 @@ const props = defineProps<{
|
|||||||
loading: boolean
|
loading: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
// Sort environments alphabetically by default
|
||||||
|
|
||||||
|
const alphabeticallySortedTeamEnvironments = computed(() =>
|
||||||
|
sortTeamEnvironmentsAlphabetically(props.teamEnvironments, "asc")
|
||||||
|
)
|
||||||
|
|
||||||
const showModalImportExport = ref(false)
|
const showModalImportExport = ref(false)
|
||||||
const showModalDetails = ref(false)
|
const showModalDetails = ref(false)
|
||||||
const action = ref<"new" | "edit">("edit")
|
const action = ref<"new" | "edit">("edit")
|
||||||
@@ -209,11 +222,12 @@ defineActionHandler(
|
|||||||
"modals.team.environment.edit",
|
"modals.team.environment.edit",
|
||||||
({ envName, variableName, isSecret }) => {
|
({ envName, variableName, isSecret }) => {
|
||||||
if (variableName) editingVariableName.value = variableName
|
if (variableName) editingVariableName.value = variableName
|
||||||
const teamEnvToEdit = props.teamEnvironments.find(
|
const teamEnvToEdit = alphabeticallySortedTeamEnvironments.value.find(
|
||||||
(environment) => environment.environment.name === envName
|
({ env }) => env.environment.name === envName
|
||||||
)
|
)
|
||||||
if (teamEnvToEdit) {
|
if (teamEnvToEdit) {
|
||||||
editEnvironment(teamEnvToEdit)
|
const { env } = teamEnvToEdit
|
||||||
|
editEnvironment(env)
|
||||||
secretOptionSelected.value = isSecret ?? false
|
secretOptionSelected.value = isSecret ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Environment } from "@hoppscotch/data"
|
||||||
|
import { TeamEnvironment } from "../teams/TeamEnvironment"
|
||||||
|
|
||||||
|
type SortOrder = "asc" | "desc"
|
||||||
|
|
||||||
|
type EnvironmentWithIndex<T> = {
|
||||||
|
env: T
|
||||||
|
index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts an array of environments alphabetically based on a specified name getter function.
|
||||||
|
*
|
||||||
|
* @template T - The type of the environments array elements.
|
||||||
|
* @param {T[]} environments - The array of environments to be sorted.
|
||||||
|
* @param {SortOrder} order - The sort order, either "asc" for ascending or "desc" for descending.
|
||||||
|
* @param {(env: T) => string} getName - The function that retrieves the name from an environment entry.
|
||||||
|
* @returns {EnvironmentWithIndex<T>[]} - The sorted array of environments with their original indices.
|
||||||
|
*/
|
||||||
|
const sortEnvironmentsAlphabetically = <T>(
|
||||||
|
environments: T[],
|
||||||
|
order: SortOrder,
|
||||||
|
getName: (env: T) => string
|
||||||
|
): EnvironmentWithIndex<T>[] => {
|
||||||
|
return [...environments]
|
||||||
|
.map((env, index) => ({
|
||||||
|
env,
|
||||||
|
index,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const comparison = getName(a.env)
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.localeCompare(getName(b.env).toLocaleLowerCase())
|
||||||
|
|
||||||
|
return order === "asc" ? comparison : -comparison
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object with sorted personal environments and index.
|
||||||
|
* @param {Environment[]} environments Array of personal environments.
|
||||||
|
* @param {SortOrder} order Sorting order.
|
||||||
|
* @returns {EnvironmentWithIndex<Environment>[]} Object with sorted environments and their index.
|
||||||
|
*/
|
||||||
|
export const sortPersonalEnvironmentsAlphabetically = (
|
||||||
|
environments: Environment[],
|
||||||
|
order: SortOrder
|
||||||
|
): EnvironmentWithIndex<Environment>[] => {
|
||||||
|
return sortEnvironmentsAlphabetically<Environment>(
|
||||||
|
environments,
|
||||||
|
order,
|
||||||
|
(env) => env.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object with sorted team environments and index.
|
||||||
|
* @param environments Array of team environments.
|
||||||
|
* @param order Sorting order.
|
||||||
|
* @returns {EnvironmentWithIndex<TeamEnvironment>[]} Object with sorted environments and their index.
|
||||||
|
*/
|
||||||
|
export const sortTeamEnvironmentsAlphabetically = (
|
||||||
|
environments: TeamEnvironment[],
|
||||||
|
order: SortOrder
|
||||||
|
): EnvironmentWithIndex<TeamEnvironment>[] => {
|
||||||
|
return sortEnvironmentsAlphabetically<TeamEnvironment>(
|
||||||
|
environments,
|
||||||
|
order,
|
||||||
|
(env) => env.environment.name
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user