Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

External sources on no results page #2077

Merged
merged 9 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions src/components/VErrorSection/VNoResults.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
<template>
<div class="no-results text-center md:text-left">
<h1 class="break-words text-4xl md:text-6xl">
{{ $t("no-results.heading", { query: query.q }) }}
<h1 class="heading-4 md:heading-2 break-words">
{{ $t("no-results.heading", { query: searchTerm }) }}
</h1>
<h2
class="mt-10 text-base font-normal md:mt-16 md:text-3xl md:font-semibold"
>
<h2 class="description-regular md:heading-5 mt-4">
{{ $t("no-results.alternatives") }}
</h2>
<VExternalSourceList class="mt-4 md:mt-6" :type="type" :query="query" />

<div class="mt-10 flex flex-col flex-wrap gap-4 gap-2 md:flex-row">
<VButton
v-for="source in externalSources"
:key="source.name"
as="VLink"
:href="source.url"
variant="secondary-bordered"
class="label-bold justify-between text-dark-charcoal md:justify-start md:gap-x-2"
>
{{ source.name }}
<VIcon :icon-path="externalLinkIcon" :size="4" rtl-flip />
</VButton>
</div>
</div>
</template>

<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"

import type { MediaType } from "~/constants/media"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { ExternalSource } from "~/types/external-source"

import VButton from "~/components/VButton.vue"

import VExternalSourceList from "~/components/VExternalSearch/VExternalSourceList.vue"
import VIcon from "~/components/VIcon/VIcon.vue"

import externalLinkIcon from "~/assets/icons/external-link.svg"

export default defineComponent({
name: "VNoResults",
components: { VExternalSourceList },
components: { VIcon, VButton },
props: {
type: {
type: String as PropType<MediaType>,
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
query: {
type: Object as PropType<ApiQueryParams>,
searchTerm: {
type: String,
required: true,
},
},
setup() {
return {
externalLinkIcon,
}
},
})
</script>
37 changes: 27 additions & 10 deletions src/components/VExternalSearch/VExternalSearchForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@
class="text-base font-normal leading-[130%]"
>
<template #type>{{ $t(`external-sources.form.types.${type}`) }}</template>
<template #query>{{ query.q }}</template>
<template #query>{{ searchTerm }}</template>
</i18n>

<VExternalSourceList
class="inline-flex ms-2 md:justify-center"
:type="type"
:query="query"
:external-sources="externalSources"
/>
</section>
</template>
Expand All @@ -49,23 +48,41 @@ import {
ref,
} from "@nuxtjs/composition-api"

import type { MediaType } from "~/constants/media"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import { getFocusableElements } from "~/utils/focus-management"
import { defineEvent } from "~/types/emits"

import VExternalSourceList from "./VExternalSourceList.vue"
import type { MediaType } from "~/constants/media"

import type { ExternalSource } from "~/types/external-source"

import VExternalSourceList from "~/components/VExternalSearch/VExternalSourceList.vue"

export default defineComponent({
name: "VExternalSearchForm",
components: {
VExternalSourceList,
},
props: {
query: { type: Object as PropType<ApiQueryParams>, required: true },
type: { type: String as PropType<MediaType>, required: true },
isSupported: { type: Boolean, default: false },
hasNoResults: { type: Boolean, required: true },
type: {
type: String as PropType<MediaType>,
required: true,
},
searchTerm: {
type: String,
required: true,
},
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
isSupported: {
type: Boolean,
default: false,
},
hasNoResults: {
type: Boolean,
required: true,
},
},
emits: {
tab: defineEvent<[KeyboardEvent]>(),
Expand Down
24 changes: 8 additions & 16 deletions src/components/VExternalSearch/VExternalSourceList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
{{ $t("external-sources.caption", { openverse: "Openverse" }) }}
</p>
<VButton
v-for="source in sources"
v-for="source in externalSources"
:key="source.name"
as="VLink"
variant="plain"
Expand All @@ -60,11 +60,9 @@
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from "@nuxtjs/composition-api"
import { defineComponent, PropType } from "@nuxtjs/composition-api"

import { getAdditionalSources } from "~/utils/get-additional-sources"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { MediaType } from "~/constants/media"
import type { ExternalSource } from "~/types/external-source"

import VButton from "~/components/VButton.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
Expand All @@ -86,24 +84,18 @@ export default defineComponent({
/**
* the media type to use as the criteria for filtering additional sources
*/
type: { type: String as PropType<MediaType>, required: true },
/**
* the search query to pre-populate in the additional sources link
*/
query: { type: Object as PropType<ApiQueryParams>, required: true },
externalSources: {
type: Array as PropType<ExternalSource[]>,
required: true,
},
},
setup(props) {
const sources = computed(() =>
getAdditionalSources(props.type, props.query)
)

setup() {
return {
icons: {
externalLink: externalLinkIcon,
caretDown: caretDownIcon,
closeSmall: closeSmallIcon,
},
sources,
}
},
})
Expand Down
47 changes: 32 additions & 15 deletions src/components/VSearchGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,43 @@
:class="isAllView ? 'mb-10' : 'mb-8'"
>
<VSearchResultsTitle :size="isAllView ? 'large' : 'default'">
{{ query.q }}
{{ searchTerm }}
</VSearchResultsTitle>
</header>

<slot name="media" />

<VExternalSearchForm
:type="metaSearchFormType"
:type="externalSourcesType"
:has-no-results="hasNoResults"
:query="query"
:external-sources="externalSources"
:search-term="searchTerm"
:is-supported="supported"
@tab="$emit('tab', $event)"
/>
</section>
<VErrorSection v-else class="w-full py-10">
<template #image>
<VErrorImage :error-code="NO_RESULT" />
<VErrorImage error-code="NO_RESULT" />
</template>
<VNoResults :type="metaSearchFormType" :query="query" />
<VNoResults :external-sources="externalSources" :search-term="searchTerm" />
</VErrorSection>
</template>

<script lang="ts">
import { computed, defineComponent, PropType } from "@nuxtjs/composition-api"

import { ALL_MEDIA, IMAGE, SearchType, mediaTypes } from "~/constants/media"
import {
ALL_MEDIA,
IMAGE,
SearchType,
isSupportedMediaType,
} from "~/constants/media"
import { NO_RESULT } from "~/constants/errors"
import { defineEvent } from "~/types/emits"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import type { FetchState } from "~/types/fetch-state"
import type { ApiQueryParams } from "~/utils/search-query-transform"
import { getAdditionalSources } from "~/utils/get-additional-sources"

import VExternalSearchForm from "~/components/VExternalSearch/VExternalSearchForm.vue"
import VErrorSection from "~/components/VErrorSection/VErrorSection.vue"
Expand Down Expand Up @@ -88,29 +95,39 @@ export default defineComponent({
// noResult is hard-coded for search types that are not currently
// supported by Openverse built-in search
return props.supported
? props.query.q !== "" && props.resultsCount === 0
? Boolean(
props.query.q !== "" &&
props.fetchState.hasStarted &&
props.resultsCount === 0
)
: false
})

/**
* Metasearch form shows the external sources for current search type, or for images if the search type is 'All Content'.
* External sources search form shows the external sources for current search type, or for images if the search type is 'All Content'.
*/
const metaSearchFormType = computed(() => {
if (mediaTypes.includes(props.searchType)) {
const externalSourcesType = computed(() => {
if (isSupportedMediaType(props.searchType)) {
return props.searchType
}
return IMAGE
})

const isAllView = computed(() => {
return props.searchType === ALL_MEDIA
})
const isAllView = computed(() => props.searchType === ALL_MEDIA)

const externalSources = computed(() =>
getAdditionalSources(externalSourcesType.value, props.query)
)

const searchTerm = computed(() => props.query.q || "")

return {
hasNoResults,
metaSearchFormType,
externalSourcesType,
isAllView,
NO_RESULT,
externalSources,
searchTerm,
}
},
})
Expand Down
12 changes: 4 additions & 8 deletions src/components/VSearchResultsTitle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@
</h1>
</template>

<script>
import { defineComponent } from "@nuxtjs/composition-api"
<script lang="ts">
import { defineComponent, PropType } from "@nuxtjs/composition-api"

const sizes = {
DEFAULT: "default",
LARGE: "large",
}
export default defineComponent({
name: "VSearchResultsTitle",
props: {
size: {
required: false,
default: sizes.DEFAULT,
validator: (value) => Object.values(sizes).includes(value),
default: "default",
type: String as PropType<"default" | "large">,
},
},
})
Expand Down
5 changes: 5 additions & 0 deletions src/constants/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export function isAdditionalSearchType(
return (additionalSearchTypes as readonly string[]).includes(searchType)
}

export function isSupportedMediaType(
searchType: string
): searchType is SupportedMediaType {
return supportedMediaTypes.includes(searchType as SupportedMediaType)
}
/* Media support */

export const SUPPORTED = "supported" // Native search
Expand Down
4 changes: 4 additions & 0 deletions src/types/external-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ExternalSource {
name: string
url: string
}
3 changes: 2 additions & 1 deletion test/playwright/utils/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
IMAGE,
MediaType,
MODEL_3D,
searchPath,
SupportedSearchType,
VIDEO,
} from "~/constants/media"
Expand Down Expand Up @@ -351,7 +352,7 @@ export const goToSearchTerm = async (
const headerMode = options.headerMode ?? NEW_HEADER

if (mode === "SSR") {
const path = `search/${searchTypePath(searchType)}?q=${term}${query}`
const path = `${searchPath(searchType)}?q=${term}${query}`
await page.goto(pathWithDir(path, dir))
await dismissTranslationBanner(page)
} else {
Expand Down
35 changes: 35 additions & 0 deletions test/playwright/visual-regression/pages/no-results.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { test } from "@playwright/test"

import {
enableNewHeader,
goToSearchTerm,
languageDirections,
setCookies,
} from "~~/test/playwright/utils/navigation"
import breakpoints from "~~/test/playwright/utils/breakpoints"

import { supportedSearchTypes } from "~/constants/media"

test.describe.configure({ mode: "parallel" })

for (const searchType of supportedSearchTypes) {
for (const dir of languageDirections) {
breakpoints.describeEvery(({ breakpoint, expectSnapshot }) => {
test(`No results ${searchType} ${dir} page snapshots`, async ({
page,
}) => {
await enableNewHeader(page)
await setCookies(page.context(), {
uiBreakpoint: breakpoint as string,
uiIsFilterDismissed: true,
uiDismissedBanners: ["translation-ar"],
})
await goToSearchTerm(page, "querywithnoresults", { dir, searchType })

await expectSnapshot(`no-results-${searchType}-${dir}`, page, {
fullPage: true,
})
})
})
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"src/components/TableSortIcon.vue",
"src/components/VCopyButton.vue",
"src/components/VSketchFabViewer.vue",
"src/components/VSearchGrid.vue",

"src/components/VAudioThumbnail/**.vue",
"src/components/VAudioTrack/**.vue",
"src/components/VCheckbox/**.vue",
Expand Down