Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection Page Facets Filters #566

Open
wants to merge 31 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7631791
chore: use js-dataverse pr version
g-saracca Dec 9, 2024
1c3e033
feat: move types query param to specific get collection items enum
g-saracca Dec 9, 2024
61371a1
chore: update to new pr js-dataverse version
g-saracca Dec 9, 2024
4770017
feat: types, retrieve facets etc
g-saracca Dec 10, 2024
e1a4275
chore: update js-dataverse version
g-saracca Dec 10, 2024
c6d4f3c
feat: filtering by queries working
g-saracca Dec 10, 2024
01b3a9c
feat: seleceted facets functionality
g-saracca Dec 10, 2024
032a428
feat: improve facet group list styles
g-saracca Dec 10, 2024
52a58d3
fix: encode url facet value before setting the search params
g-saracca Dec 10, 2024
1b1ba36
feat: disable buttons while loading items
g-saracca Dec 10, 2024
2612231
feat: text align left
g-saracca Dec 11, 2024
f028bba
feat: improve list layout
g-saracca Dec 11, 2024
8c19993
feat: show diff message when filter queries applied
g-saracca Dec 11, 2024
c1f4968
feat: remove underline in less and more buttons
g-saracca Dec 11, 2024
0bc201e
feat: facet filers skeleton
g-saracca Dec 11, 2024
452c68d
feat: add locales
g-saracca Dec 11, 2024
bf2a863
feat: udpate js-dataverse and update search criteria
g-saracca Dec 12, 2024
75c6664
feat: revert noItemsMessage
g-saracca Dec 12, 2024
b819c09
feat: add stories
g-saracca Dec 12, 2024
b98a78e
fix: accesibility issues
g-saracca Dec 12, 2024
23f36bc
test: fix and add tests
g-saracca Dec 12, 2024
3089539
test: finish with unit tests
g-saracca Dec 12, 2024
a13a2c9
test: finish with e2e test
g-saracca Dec 12, 2024
e029c43
chore: file alocations tweaks
g-saracca Dec 12, 2024
50a4133
feat: add missing args
g-saracca Dec 12, 2024
b1c639c
feat: move query into collection query params
g-saracca Dec 16, 2024
d170bc8
Merge branch 'develop' into feat/562-add-collection-facets
g-saracca Dec 16, 2024
2cba117
chore: update to latest js-dv version
g-saracca Dec 18, 2024
be414ee
update with latest from develop
g-saracca Dec 18, 2024
9ab1f6e
Merge branch 'develop' into feat/562-add-collection-facets
g-saracca Dec 20, 2024
50a172a
chore: update js-dv version to latest
g-saracca Dec 20, 2024
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
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"dependencies": {
"@faker-js/faker": "7.6.0",
"@iqss/dataverse-client-javascript": "2.0.0-alpha.10",
"@iqss/dataverse-client-javascript": "2.0.0-alpha.11",
"@iqss/dataverse-design-system": "*",
"@istanbuljs/nyc-config-typescript": "1.0.2",
"@tanstack/react-table": "8.9.2",
Expand Down
2 changes: 2 additions & 0 deletions public/locales/en/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"error": "There was an error publishing your collection."
},
"publishedAlert": "Your collection is now public.",
"addFacetFilter": "Add {{labelName}} facet filter",
"removeSelectedFacet": "Remove {{labelName}} facet filter",
"share": {
"shareCollection": "Share Collection",
"helpText": "Share this collection on your favorite social media networks."
Expand Down
2 changes: 2 additions & 0 deletions public/locales/en/shared.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"cancel": "Cancel",
"close": "Close",
"continue": "Continue",
"more": "More...",
"less": "Less...",
"share": "Share",
"pageNumberNotFound": {
"heading": "Page Number Not Found",
Expand Down
12 changes: 12 additions & 0 deletions src/collection/domain/models/CollectionItemSubset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@ import { FileItemTypePreview } from '../../../files/domain/models/FileItemTypePr

export interface CollectionItemSubset {
items: CollectionItem[]
facets: CollectionItemsFacet[]
totalItemCount: number
}

export type CollectionItem =
| CollectionItemTypePreview
| DatasetItemTypePreview
| FileItemTypePreview

export interface CollectionItemsFacet {
name: string
friendlyName: string
labels: CollectionItemsFacetLabel[]
}

interface CollectionItemsFacetLabel {
name: string
count: number
}
8 changes: 8 additions & 0 deletions src/collection/domain/models/CollectionItemsQueryParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export enum CollectionItemsQueryParams {
SORT = 'sort',
ORDER = 'order',
START = 'start',
TYPES = 'types',
QUERY = 'q',
FILTER_QUERIES = 'fqs'
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class CollectionJSDataverseRepository implements CollectionRepository {

return {
items: collectionItemsPreviewsMapped,
facets: jsCollectionItemSubset.facets,
totalItemCount: jsCollectionItemSubset.totalItemCount
}
})
Expand Down
2 changes: 0 additions & 2 deletions src/sections/Route.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export const RouteWithParams = {
export enum QueryParamKey {
VERSION = 'version',
PERSISTENT_ID = 'persistentId',
QUERY = 'q',
COLLECTION_ITEM_TYPES = 'types',
g-saracca marked this conversation as resolved.
Show resolved Hide resolved
PAGE = 'page',
COLLECTION_ID = 'collectionId'
}
22 changes: 16 additions & 6 deletions src/sections/collection/CollectionHelper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Collection } from '@/collection/domain/models/Collection'
import { CollectionItemsQueryParams } from '@/collection/domain/models/CollectionItemsQueryParams'
import { FilterQuery } from '@/collection/domain/models/CollectionSearchCriteria'
import { CollectionItemType } from '@/collection/domain/models/CollectionItemType'
import { QueryParamKey } from '../Route.enum'
import { Collection } from '@/collection/domain/models/Collection'
import { UpwardHierarchyNode } from '@/shared/hierarchy/domain/models/UpwardHierarchyNode'

export class CollectionHelper {
Expand All @@ -9,11 +10,11 @@ export class CollectionHelper {
? parseInt(searchParams.get('page') as string, 10)
: 1

const searchQuery = searchParams.get(QueryParamKey.QUERY)
? decodeURIComponent(searchParams.get(QueryParamKey.QUERY) as string)
const searchQuery = searchParams.get(CollectionItemsQueryParams.QUERY)
? decodeURIComponent(searchParams.get(CollectionItemsQueryParams.QUERY) as string)
: undefined

const typesParam = searchParams.get(QueryParamKey.COLLECTION_ITEM_TYPES) ?? undefined
const typesParam = searchParams.get(CollectionItemsQueryParams.TYPES) ?? undefined

const typesQuery = typesParam
?.split(',')
Expand All @@ -22,7 +23,16 @@ export class CollectionHelper {
Object.values(CollectionItemType).includes(type as CollectionItemType)
) as CollectionItemType[]

return { pageQuery, searchQuery, typesQuery }
const filtersParam = searchParams.get(CollectionItemsQueryParams.FILTER_QUERIES) ?? undefined

const filtersQuery: FilterQuery[] | undefined = filtersParam
? (filtersParam
?.split(',')
.map((filterQuery) => decodeURIComponent(filterQuery))
.filter((decodedFilter) => /^[^:]+:[^:]+$/.test(decodedFilter)) as FilterQuery[])
: undefined

return { pageQuery, searchQuery, typesQuery, filtersQuery }
}

static isRootCollection(collectionHierarchy: Collection['hierarchy']) {
Expand Down
165 changes: 125 additions & 40 deletions src/sections/collection/collection-items-panel/CollectionItemsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { useEffect, useRef, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Stack } from '@iqss/dataverse-design-system'
import { CollectionRepository } from '@/collection/domain/repositories/CollectionRepository'
import { CollectionItemsPaginationInfo } from '@/collection/domain/models/CollectionItemsPaginationInfo'
import { CollectionSearchCriteria } from '@/collection/domain/models/CollectionSearchCriteria'
import {
CollectionSearchCriteria,
FilterQuery
} from '@/collection/domain/models/CollectionSearchCriteria'
import { CollectionItemType } from '@/collection/domain/models/CollectionItemType'
import { CollectionItemsQueryParams } from '@/collection/domain/models/CollectionItemsQueryParams'
import { useGetAccumulatedItems } from './useGetAccumulatedItems'
import { UseCollectionQueryParamsReturnType } from '../useGetCollectionQueryParams'
import { useLoadMoreOnPopStateEvent } from './useLoadMoreOnPopStateEvent'
import { useLoading } from '@/sections/loading/LoadingContext'
import { QueryParamKey } from '../../Route.enum'
import { CollectionHelper } from '../CollectionHelper'
import { FilterPanel } from './filter-panel/FilterPanel'
import { ItemsList } from './items-list/ItemsList'
import { SearchPanel } from './search-panel/SearchPanel'
import { ItemTypeChange } from './filter-panel/type-filters/TypeFilters'
import { RemoveAddFacetFilter } from './filter-panel/facets-filters/FacetFilterGroup'
import { SelectedFacets } from './selected-facets/SelectedFacets'
import styles from './CollectionItemsPanel.module.scss'

interface CollectionItemsPanelProps {
Expand All @@ -30,7 +36,8 @@ interface CollectionItemsPanelProps {
* 2. When the user scrolls to the bottom of the list and there are more items to load
* 3. When the user submits a search query in the search panel
* 4. When the user changes the item types in the filter panel
* 5. When the user navigates back and forward in the browser
* 5. When the user selects or removes a facet filter
* 6. When the user navigates back and forward in the browser
*
* It initializes the search criteria with the query params in the URL.
* By default if no query params are present in the URL, the search query is empty and the item types are COLLECTION and DATASET.
Expand All @@ -51,7 +58,10 @@ export const CollectionItemsPanel = ({
// This object will update every time we update a query param in the URL with the setSearchParams setter
const currentSearchCriteria = new CollectionSearchCriteria(
collectionQueryParams.searchQuery,
collectionQueryParams.typesQuery || [CollectionItemType.COLLECTION, CollectionItemType.DATASET]
collectionQueryParams.typesQuery || [CollectionItemType.COLLECTION, CollectionItemType.DATASET],
undefined,
undefined,
collectionQueryParams.filtersQuery
)

const [paginationInfo, setPaginationInfo] = useState<CollectionItemsPaginationInfo>(
Expand All @@ -62,6 +72,7 @@ export const CollectionItemsPanel = ({
const {
isLoadingItems,
accumulatedItems,
facets,
totalAvailable,
hasNextPage,
error,
Expand Down Expand Up @@ -97,20 +108,23 @@ export const CollectionItemsPanel = ({
if (searchValue === '') {
// Update the URL without the search value, keep other querys
setSearchParams((currentSearchParams) => {
currentSearchParams.delete(QueryParamKey.QUERY)
currentSearchParams.delete(CollectionItemsQueryParams.QUERY)
return currentSearchParams
})
} else {
// Update the URL with the search value ,keep other querys and include all item types always
setSearchParams((currentSearchParams) => ({
...currentSearchParams,
[QueryParamKey.COLLECTION_ITEM_TYPES]: [
CollectionItemType.COLLECTION,
CollectionItemType.DATASET,
CollectionItemType.FILE
].join(','),
[QueryParamKey.QUERY]: searchValue
}))
setSearchParams((currentSearchParams) => {
currentSearchParams.set(
CollectionItemsQueryParams.TYPES,
[CollectionItemType.COLLECTION, CollectionItemType.DATASET, CollectionItemType.FILE].join(
','
)
)

currentSearchParams.set(CollectionItemsQueryParams.QUERY, searchValue)

return currentSearchParams
})
}

// WHEN SEARCHING, WE RESET THE PAGINATION INFO AND KEEP ALL ITEM TYPES!!
Expand All @@ -137,24 +151,73 @@ export const CollectionItemsPanel = ({
(itemType) => itemType !== type
)

// KEEP SEARCH VALUE IF EXISTS
itemsListContainerRef.current?.scrollTo({ top: 0 })

const resetPaginationInfo = new CollectionItemsPaginationInfo()
setPaginationInfo(resetPaginationInfo)

// Update the URL with the new item types, keep other querys and include the search value if exists
setSearchParams((currentSearchParams) => ({
...currentSearchParams,
[QueryParamKey.COLLECTION_ITEM_TYPES]: newItemsTypes.join(','),
...(currentSearchCriteria.searchText && {
[QueryParamKey.QUERY]: currentSearchCriteria.searchText
})
}))
// Update the URL with the new item types, keep other querys
setSearchParams((currentSearchParams) => {
currentSearchParams.set(CollectionItemsQueryParams.TYPES, newItemsTypes.join(','))

return currentSearchParams
})

const newCollectionSearchCriteria = new CollectionSearchCriteria(
currentSearchCriteria.searchText,
newItemsTypes
newItemsTypes,
undefined,
undefined,
currentSearchCriteria.filterQueries
)

const totalItemsCount = await loadMore(resetPaginationInfo, newCollectionSearchCriteria, true)

if (totalItemsCount !== undefined) {
const paginationInfoUpdated = resetPaginationInfo.withTotal(totalItemsCount)
setPaginationInfo(paginationInfoUpdated)
}
}

const handleFacetChange = async (filterQuery: FilterQuery, removeOrAdd: RemoveAddFacetFilter) => {
const newFilterQueries =
removeOrAdd === RemoveAddFacetFilter.ADD
? [
...new Set([
...(currentSearchCriteria?.filterQueries ?? /* istanbul ignore next */ []),
filterQuery
])
]
: (currentSearchCriteria.filterQueries ?? /* istanbul ignore next */ []).filter(
(fQuery) => fQuery !== filterQuery
)

itemsListContainerRef.current?.scrollTo({ top: 0 })

const resetPaginationInfo = new CollectionItemsPaginationInfo()
setPaginationInfo(resetPaginationInfo)

const newFilterQueriesWithFacetValueEncoded = newFilterQueries.map((fq) => {
const [facetName, facetValue] = fq.split(':')
return `${facetName}:${encodeURIComponent(facetValue)}`
})

// Update the URL with the new facets, keep other querys and include the search value if exists
setSearchParams((currentSearchParams) => {
currentSearchParams.set(
CollectionItemsQueryParams.FILTER_QUERIES,
newFilterQueriesWithFacetValueEncoded.join(',')
)

return currentSearchParams
})

const newCollectionSearchCriteria = new CollectionSearchCriteria(
currentSearchCriteria.searchText,
currentSearchCriteria.itemTypes,
undefined,
undefined,
newFilterQueries
)

const totalItemsCount = await loadMore(resetPaginationInfo, newCollectionSearchCriteria, true)
Expand All @@ -171,7 +234,10 @@ export const CollectionItemsPanel = ({

const newCollectionSearchCriteria = new CollectionSearchCriteria(
collectionQueryParams.searchQuery,
collectionQueryParams.typesQuery
collectionQueryParams.typesQuery,
undefined,
undefined,
collectionQueryParams.filtersQuery
)

const newPaginationInfo = new CollectionItemsPaginationInfo()
Expand All @@ -183,6 +249,9 @@ export const CollectionItemsPanel = ({
}
}

const showSelectedFacets =
currentSearchCriteria.filterQueries && currentSearchCriteria.filterQueries.length > 0

useEffect(() => {
setIsLoading(isLoadingItems)
}, [isLoadingItems, setIsLoading])
Expand All @@ -202,24 +271,40 @@ export const CollectionItemsPanel = ({
<FilterPanel
currentItemTypes={currentSearchCriteria.itemTypes}
onItemTypesChange={handleItemsTypeChange}
currentFilterQueries={currentSearchCriteria.filterQueries}
facets={facets}
onFacetChange={handleFacetChange}
isLoadingCollectionItems={isLoadingItems}
/>

<ItemsList
parentCollectionAlias={collectionId}
items={accumulatedItems}
error={error}
accumulatedCount={accumulatedCount}
isLoadingItems={isLoadingItems}
areItemsAvailable={areItemsAvailable}
hasNextPage={hasNextPage}
isEmptyItems={isEmptyItems}
hasSearchValue={currentSearchCriteria.hasSearchText()}
itemsTypesSelected={currentSearchCriteria.itemTypes as CollectionItemType[]}
paginationInfo={paginationInfo}
onBottomReach={handleLoadMoreOnBottomReach}
ref={itemsListContainerRef}
/>
<Stack direction="vertical" gap={2}>
{showSelectedFacets && facets.length > 0 && (
<SelectedFacets
onRemoveFacet={(filterQuery: FilterQuery) =>
handleFacetChange(filterQuery, RemoveAddFacetFilter.REMOVE)
}
selectedFilterQueries={currentSearchCriteria.filterQueries}
isLoadingCollectionItems={isLoadingItems}
/>
)}

<ItemsList
parentCollectionAlias={collectionId}
items={accumulatedItems}
error={error}
accumulatedCount={accumulatedCount}
isLoadingItems={isLoadingItems}
areItemsAvailable={areItemsAvailable}
hasNextPage={hasNextPage}
isEmptyItems={isEmptyItems}
hasSearchValue={currentSearchCriteria.hasSearchText()}
itemsTypesSelected={currentSearchCriteria.itemTypes as CollectionItemType[]}
filterQueriesSelected={currentSearchCriteria.filterQueries ?? []}
paginationInfo={paginationInfo}
onBottomReach={handleLoadMoreOnBottomReach}
ref={itemsListContainerRef}
/>
</Stack>
</div>
</section>
)
Expand Down
Loading
Loading