feat: filter and group history entries
This commit is contained in:
11
packages/hoppscotch-app/src/components.d.ts
vendored
11
packages/hoppscotch-app/src/components.d.ts
vendored
@@ -97,17 +97,6 @@ declare module '@vue/runtime-core' {
|
||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||
IconLucideLoader: typeof import('~icons/lucide/loader')['default']
|
||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
||||
|
||||
@@ -16,6 +16,34 @@
|
||||
:title="t('app.wiki')"
|
||||
:icon="IconHelpCircle"
|
||||
/>
|
||||
<tippy interactive trigger="click" theme="popover">
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.filter')"
|
||||
:icon="IconFilter"
|
||||
/>
|
||||
<template #content="{ hide }">
|
||||
<div ref="tippyActions" class="flex flex-col focus:outline-none">
|
||||
<div class="pb-2 pl-4 text-tiny text-secondaryLight">
|
||||
{{ t("action.filter") }}
|
||||
</div>
|
||||
<SmartRadioGroup
|
||||
v-model="filterSelection"
|
||||
:radios="filters"
|
||||
@update:model-value="hide()"
|
||||
/>
|
||||
<hr />
|
||||
<div class="pb-2 pl-4 text-tiny text-secondaryLight">
|
||||
{{ t("action.group_by") }}
|
||||
</div>
|
||||
<SmartRadioGroup
|
||||
v-model="groupSelection"
|
||||
:radios="groups"
|
||||
@update:model-value="hide()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</tippy>
|
||||
<ButtonSecondary
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
data-testid="clear_history"
|
||||
@@ -36,13 +64,18 @@
|
||||
open
|
||||
>
|
||||
<summary
|
||||
class="flex items-center justify-between flex-1 min-w-0 cursor-pointer transition focus:outline-none text-secondaryLight text-tiny group"
|
||||
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
||||
>
|
||||
<span
|
||||
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary"
|
||||
>
|
||||
<icon-lucide-chevron-right class="mr-2 indicator" />
|
||||
<span class="truncate capitalize-first">
|
||||
<span
|
||||
:class="[
|
||||
{ 'capitalize-first': groupSelection === 'TIME' },
|
||||
'truncate',
|
||||
]"
|
||||
>
|
||||
{{ filteredHistoryGroupIndex }}
|
||||
</span>
|
||||
</span>
|
||||
@@ -68,15 +101,6 @@
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
<div
|
||||
v-if="!(filteredHistory.length !== 0 || history.length === 0)"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<span class="my-2 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="history.length === 0"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
@@ -91,6 +115,28 @@
|
||||
{{ t("empty.history") }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
Object.keys(filteredHistoryGroups).length === 0 ||
|
||||
filteredHistory.length === 0
|
||||
"
|
||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||
>
|
||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||
<span class="mt-2 mb-4 text-center">
|
||||
{{ t("state.nothing_found") }} "{{ filterText || filterSelection }}"
|
||||
</span>
|
||||
<ButtonSecondary
|
||||
:label="t('action.clear')"
|
||||
outline
|
||||
@click="
|
||||
() => {
|
||||
filterText = ''
|
||||
filterSelection = 'ALL'
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<SmartConfirmModal
|
||||
:show="confirmRemove"
|
||||
:title="`${t('confirm.remove_history')}`"
|
||||
@@ -115,14 +161,16 @@
|
||||
import IconHelpCircle from "~icons/lucide/help-circle"
|
||||
import IconTrash2 from "~icons/lucide/trash-2"
|
||||
import IconTrash from "~icons/lucide/trash"
|
||||
import IconFilter from "~icons/lucide/filter"
|
||||
import { computed, ref, Ref } from "vue"
|
||||
import { useColorMode } from "@composables/theming"
|
||||
import {
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
isEqualHoppRESTRequest,
|
||||
safelyExtractRESTRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { groupBy, escapeRegExp } from "lodash-es"
|
||||
import { groupBy, escapeRegExp, filter } from "lodash-es"
|
||||
import { useTimeAgo } from "@vueuse/core"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -229,10 +277,44 @@ const filteredHistory = computed(() =>
|
||||
)
|
||||
)
|
||||
|
||||
const filters = computed(() => [
|
||||
{ value: "ALL" as const, label: t("filter.all") },
|
||||
{ value: "STARRED" as const, label: t("filter.starred") },
|
||||
])
|
||||
|
||||
type FilterMode = typeof filters["value"][number]["value"]
|
||||
|
||||
const filterSelection = ref<FilterMode>("ALL")
|
||||
|
||||
const groups = computed(() => [
|
||||
{ value: "TIME" as const, label: t("group.time") },
|
||||
{ value: "URL" as const, label: t("group.url") },
|
||||
])
|
||||
|
||||
type GroupMode = typeof groups["value"][number]["value"]
|
||||
|
||||
const groupSelection = ref<GroupMode>("TIME")
|
||||
|
||||
const filteredHistoryGroups = computed(() =>
|
||||
groupBy(filteredHistory.value, (entry) => entry.timeAgo.value)
|
||||
groupBy(
|
||||
filter(filteredHistory.value, (input) =>
|
||||
filterSelection.value === "STARRED" ? input.entry.star : true
|
||||
),
|
||||
(input) =>
|
||||
groupSelection.value === "TIME"
|
||||
? input.timeAgo.value
|
||||
: getAppropriateURL(input.entry)
|
||||
)
|
||||
)
|
||||
|
||||
const getAppropriateURL = (entry: HistoryEntry) => {
|
||||
if (props.page === "rest") {
|
||||
return (entry.request as HoppRESTRequest).endpoint
|
||||
} else if (props.page === "graphql") {
|
||||
return (entry.request as HoppGQLRequest).url
|
||||
}
|
||||
}
|
||||
|
||||
const clearHistory = () => {
|
||||
if (props.page === "rest") clearRESTHistory()
|
||||
else clearGraphqlHistory()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<ButtonSecondary
|
||||
v-if="response.body"
|
||||
v-tippy="{ theme: 'tooltip' }"
|
||||
:title="t('action.filter_response')"
|
||||
:title="t('action.filter')"
|
||||
:icon="IconFilter"
|
||||
:class="{ '!text-accent': toggleFilter }"
|
||||
@click.prevent="toggleFilterState"
|
||||
|
||||
Reference in New Issue
Block a user