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",
|
||||
"set": "Set environment",
|
||||
"set_as_environment": "Set as environment",
|
||||
"short_name": "Environment needs to have minimum 3 characters",
|
||||
"team_environments": "Workspace Environments",
|
||||
"title": "Environments",
|
||||
"updated": "Environment updated",
|
||||
|
||||
@@ -77,24 +77,27 @@
|
||||
:label="`${t('environment.my_environments')}`"
|
||||
>
|
||||
<HoppSmartItem
|
||||
v-for="(gen, index) in myEnvironments"
|
||||
v-for="{
|
||||
env,
|
||||
index,
|
||||
} in alphabeticallySortedPersonalEnvironments"
|
||||
:key="`gen-${index}`"
|
||||
:icon="IconLayers"
|
||||
:label="gen.name"
|
||||
:label="env.name"
|
||||
:info-icon="isEnvActive(index) ? IconCheck : undefined"
|
||||
:active-info-icon="isEnvActive(index)"
|
||||
@click="
|
||||
() => {
|
||||
handleEnvironmentChange(index, {
|
||||
type: 'my-environment',
|
||||
environment: gen,
|
||||
environment: env,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="myEnvironments.length === 0"
|
||||
v-if="alphabeticallySortedPersonalEnvironments.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
@@ -116,24 +119,24 @@
|
||||
</div>
|
||||
<div v-if="isTeamSelected" class="flex flex-col">
|
||||
<HoppSmartItem
|
||||
v-for="(gen, index) in teamEnvironmentList"
|
||||
v-for="{ env, index } in alphabeticallySortedTeamEnvironments"
|
||||
:key="`gen-team-${index}`"
|
||||
:icon="IconLayers"
|
||||
:label="gen.environment.name"
|
||||
:info-icon="isEnvActive(gen.id) ? IconCheck : undefined"
|
||||
:active-info-icon="isEnvActive(gen.id)"
|
||||
:label="env.environment.name"
|
||||
:info-icon="isEnvActive(env.id) ? IconCheck : undefined"
|
||||
:active-info-icon="isEnvActive(env.id)"
|
||||
@click="
|
||||
() => {
|
||||
handleEnvironmentChange(index, {
|
||||
type: 'team-environment',
|
||||
environment: gen,
|
||||
environment: env,
|
||||
})
|
||||
hide()
|
||||
}
|
||||
"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="teamEnvironmentList.length === 0"
|
||||
v-if="alphabeticallySortedTeamEnvironments.length === 0"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
@@ -316,6 +319,10 @@ import { useLocalState } from "~/newstore/localstate"
|
||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
||||
import { useService } from "dioc/vue"
|
||||
import { WorkspaceService } from "~/services/workspace.service"
|
||||
import {
|
||||
sortPersonalEnvironmentsAlphabetically,
|
||||
sortTeamEnvironmentsAlphabetically,
|
||||
} from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||
|
||||
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 = (
|
||||
index: number,
|
||||
env?:
|
||||
|
||||
@@ -386,6 +386,11 @@ const saveEnvironment = () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (editingName.value.length < 3) {
|
||||
toast.error(`${t("environment.short_name")}`)
|
||||
return
|
||||
}
|
||||
|
||||
const filteredVariables = pipe(
|
||||
vars.value,
|
||||
A.filterMap(
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<EnvironmentsMyEnvironment
|
||||
v-for="(environment, index) in environments"
|
||||
v-for="{ env, index } in alphabeticallySortedPersonalEnvironments"
|
||||
:key="`environment-${index}`"
|
||||
:environment-index="index"
|
||||
:environment="environment"
|
||||
:environment="env"
|
||||
@edit-environment="editEnvironment(index)"
|
||||
/>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!environments.length"
|
||||
v-if="!alphabeticallySortedPersonalEnvironments.length"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
@@ -79,7 +79,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { ref, computed } from "vue"
|
||||
import { environments$ } from "~/newstore/environments"
|
||||
import { useColorMode } from "~/composables/theming"
|
||||
import { useReadonlyStream } from "@composables/stream"
|
||||
@@ -87,14 +87,19 @@ import { useI18n } from "~/composables/i18n"
|
||||
import IconPlus from "~icons/lucide/plus"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import { Environment } from "@hoppscotch/data"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { sortPersonalEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||
|
||||
const t = useI18n()
|
||||
const colorMode = useColorMode()
|
||||
|
||||
const environments = useReadonlyStream(environments$, [])
|
||||
|
||||
// Sort environments alphabetically by default
|
||||
const alphabeticallySortedPersonalEnvironments = computed(() =>
|
||||
sortPersonalEnvironmentsAlphabetically(environments.value, "asc")
|
||||
)
|
||||
|
||||
const showModalImportExport = ref(false)
|
||||
const showModalDetails = ref(false)
|
||||
const action = ref<"new" | "edit">("edit")
|
||||
@@ -130,11 +135,10 @@ defineActionHandler(
|
||||
"modals.my.environment.edit",
|
||||
({ envName, variableName, isSecret }) => {
|
||||
if (variableName) editingVariableName.value = variableName
|
||||
const envIndex: number = environments.value.findIndex(
|
||||
(environment: Environment) => {
|
||||
return environment.name === envName
|
||||
}
|
||||
)
|
||||
const envIndex: number =
|
||||
alphabeticallySortedPersonalEnvironments.value.findIndex(({ env }) => {
|
||||
return env.name === envName
|
||||
})
|
||||
if (envName !== "Global") {
|
||||
editEnvironment(envIndex)
|
||||
secretOptionSelected.value = isSecret ?? false
|
||||
|
||||
@@ -480,6 +480,8 @@ const getErrorMessage = (err: GQLError<string>) => {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="group flex items-stretch"
|
||||
@contextmenu.prevent="options!.tippy.show()"
|
||||
@contextmenu.prevent="options!.tippy?.show()"
|
||||
>
|
||||
<span
|
||||
class="flex cursor-pointer items-center justify-center px-4"
|
||||
@@ -212,6 +212,8 @@ const getErrorMessage = (err: GQLError<string>) => {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -44,7 +44,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<HoppSmartPlaceholder
|
||||
v-if="!loading && !teamEnvironments.length && !adapterError"
|
||||
v-if="
|
||||
!loading &&
|
||||
!alphabeticallySortedTeamEnvironments.length &&
|
||||
!adapterError
|
||||
"
|
||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
||||
:alt="`${t('empty.environments')}`"
|
||||
:text="t('empty.environments')"
|
||||
@@ -79,15 +83,15 @@
|
||||
</HoppSmartPlaceholder>
|
||||
<div v-else-if="!loading">
|
||||
<EnvironmentsTeamsEnvironment
|
||||
v-for="(environment, index) in JSON.parse(
|
||||
JSON.stringify(teamEnvironments)
|
||||
v-for="{ env, index } in JSON.parse(
|
||||
JSON.stringify(alphabeticallySortedTeamEnvironments)
|
||||
)"
|
||||
:key="`environment-${index}`"
|
||||
:environment="environment"
|
||||
:environment="env"
|
||||
:is-viewer="team?.role === 'VIEWER'"
|
||||
@edit-environment="editEnvironment(environment)"
|
||||
@edit-environment="editEnvironment(env)"
|
||||
@show-environment-properties="
|
||||
showEnvironmentProperties(environment.environment.id)
|
||||
showEnvironmentProperties(env.environment.id)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -114,7 +118,9 @@
|
||||
/>
|
||||
<EnvironmentsImportExport
|
||||
v-if="showModalImportExport"
|
||||
:team-environments="teamEnvironments"
|
||||
:team-environments="
|
||||
alphabeticallySortedTeamEnvironments.map(({ env }) => env)
|
||||
"
|
||||
:team-id="team?.teamID"
|
||||
environment-type="TEAM_ENV"
|
||||
@hide-modal="displayModalImportExport(false)"
|
||||
@@ -139,6 +145,7 @@ import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconImport from "~icons/lucide/folder-down"
|
||||
import { defineActionHandler } from "~/helpers/actions"
|
||||
import { TeamWorkspace } from "~/services/workspace.service"
|
||||
import { sortTeamEnvironmentsAlphabetically } from "~/helpers/utils/sortEnvironmentsAlphabetically"
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
@@ -151,6 +158,12 @@ const props = defineProps<{
|
||||
loading: boolean
|
||||
}>()
|
||||
|
||||
// Sort environments alphabetically by default
|
||||
|
||||
const alphabeticallySortedTeamEnvironments = computed(() =>
|
||||
sortTeamEnvironmentsAlphabetically(props.teamEnvironments, "asc")
|
||||
)
|
||||
|
||||
const showModalImportExport = ref(false)
|
||||
const showModalDetails = ref(false)
|
||||
const action = ref<"new" | "edit">("edit")
|
||||
@@ -209,11 +222,12 @@ defineActionHandler(
|
||||
"modals.team.environment.edit",
|
||||
({ envName, variableName, isSecret }) => {
|
||||
if (variableName) editingVariableName.value = variableName
|
||||
const teamEnvToEdit = props.teamEnvironments.find(
|
||||
(environment) => environment.environment.name === envName
|
||||
const teamEnvToEdit = alphabeticallySortedTeamEnvironments.value.find(
|
||||
({ env }) => env.environment.name === envName
|
||||
)
|
||||
if (teamEnvToEdit) {
|
||||
editEnvironment(teamEnvToEdit)
|
||||
const { env } = teamEnvToEdit
|
||||
editEnvironment(env)
|
||||
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