feat: filter and group history entries

This commit is contained in:
Liyas Thomas
2022-10-30 17:05:32 +05:30
parent 79ada82223
commit 2f4c39d310
36 changed files with 450 additions and 59 deletions

View File

@@ -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']

View File

@@ -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()

View File

@@ -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"