diff --git a/packages/dataviews/src/components/dataviews/stories/fixtures.js b/packages/dataviews/src/components/dataviews/stories/fixtures.js index 01351b84848fc1..7e084d6a017ad2 100644 --- a/packages/dataviews/src/components/dataviews/stories/fixtures.js +++ b/packages/dataviews/src/components/dataviews/stories/fixtures.js @@ -23,6 +23,7 @@ export const data = [ type: 'Not a planet', categories: [ 'Space', 'NASA' ], satellites: 0, + satellites_no_type: 0, }, { id: 2, @@ -32,6 +33,7 @@ export const data = [ type: 'Not a planet', categories: [ 'Space' ], satellites: 0, + satellites_no_type: 0, }, { id: 3, @@ -41,6 +43,7 @@ export const data = [ type: 'Not a planet', categories: [ 'NASA' ], satellites: 0, + satellites_no_type: 0, }, { id: 4, @@ -50,6 +53,7 @@ export const data = [ type: 'Ice giant', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 14, + satellites_no_type: 14, }, { id: 5, @@ -59,6 +63,7 @@ export const data = [ type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 0, + satellites_no_type: 0, }, { id: 6, @@ -68,6 +73,7 @@ export const data = [ type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 0, + satellites_no_type: 0, }, { id: 7, @@ -77,6 +83,7 @@ export const data = [ type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 1, + satellites_no_type: 1, }, { id: 8, @@ -86,6 +93,7 @@ export const data = [ type: 'Terrestrial', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 2, + satellites_no_type: 2, }, { id: 9, @@ -95,6 +103,7 @@ export const data = [ type: 'Gas giant', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 95, + satellites_no_type: 95, }, { id: 10, @@ -104,6 +113,7 @@ export const data = [ type: 'Gas giant', categories: [ 'Space', 'Planet', 'Solar system' ], satellites: 146, + satellites_no_type: 146, }, { id: 11, @@ -113,6 +123,7 @@ export const data = [ type: 'Ice giant', categories: [ 'Space', 'Ice giant', 'Solar system' ], satellites: 28, + satellites_no_type: 28, }, ]; @@ -189,6 +200,12 @@ export const fields = [ { label: 'Satellites', id: 'satellites', + type: 'integer', + enableSorting: true, + }, + { + label: 'Satellites (no type)', + id: 'satellites_no_type', enableSorting: true, }, { diff --git a/packages/dataviews/src/field-types/index.tsx b/packages/dataviews/src/field-types/index.tsx new file mode 100644 index 00000000000000..1ca5a12bd23de0 --- /dev/null +++ b/packages/dataviews/src/field-types/index.tsx @@ -0,0 +1,22 @@ +/** + * Internal dependencies + */ +import { default as integer } from './integer'; +import type { FieldType } from '../types'; + +/** + * + * @param {FieldType} type The field type definition to get. + * + * @return A field type definition. + */ +export default function getFieldTypeDefinition( type?: FieldType ) { + if ( 'integer' === type ) { + return integer; + } + + // If no type found, the sort function doesn't do anything. + return { + sort: () => 0, + }; +} diff --git a/packages/dataviews/src/field-types/integer.tsx b/packages/dataviews/src/field-types/integer.tsx new file mode 100644 index 00000000000000..36d0b445d6275f --- /dev/null +++ b/packages/dataviews/src/field-types/integer.tsx @@ -0,0 +1,12 @@ +/** + * Internal dependencies + */ +import type { SortDirection } from '../types'; + +function sort( a: any, b: any, direction: SortDirection ) { + return direction === 'asc' ? a - b : b - a; +} + +export default { + sort, +}; diff --git a/packages/dataviews/src/filter-and-sort-data-view.ts b/packages/dataviews/src/filter-and-sort-data-view.ts index 000ec110f7d0db..b716b499e4a0f4 100644 --- a/packages/dataviews/src/filter-and-sort-data-view.ts +++ b/packages/dataviews/src/filter-and-sort-data-view.ts @@ -143,6 +143,15 @@ export function filterSortAndPaginate< Item >( const valueA = fieldToSort.getValue( { item: a } ) ?? ''; const valueB = fieldToSort.getValue( { item: b } ) ?? ''; + if ( fieldToSort.type === 'integer' ) { + return fieldToSort.sort( + a, + b, + view.sort?.direction ?? 'desc' + ); + } + + // When/if types become required, we can remove the following logic. if ( typeof valueA === 'number' && typeof valueB === 'number' diff --git a/packages/dataviews/src/normalize-fields.ts b/packages/dataviews/src/normalize-fields.ts index b7407dfe522fb6..6cb36de1029005 100644 --- a/packages/dataviews/src/normalize-fields.ts +++ b/packages/dataviews/src/normalize-fields.ts @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import getFieldTypeDefinition from './field-types'; import type { Field, NormalizedField, ItemRecord } from './types'; /** @@ -13,15 +14,28 @@ export function normalizeFields< Item >( fields: Field< Item >[] ): NormalizedField< Item >[] { return fields.map( ( field ) => { + const fieldTypeDefinition = getFieldTypeDefinition( field.type ); + const getValue = field.getValue || ( ( { item }: { item: ItemRecord } ) => item[ field.id ] ); + const sort = + field.sort ?? + function sort( a, b, direction ) { + return fieldTypeDefinition.sort( + getValue( { item: a } ), + getValue( { item: b } ), + direction + ); + }; + return { ...field, label: field.label || field.id, getValue, render: field.render || getValue, + sort, }; } ); } diff --git a/packages/dataviews/src/test/filter-and-sort-data-view.js b/packages/dataviews/src/test/filter-and-sort-data-view.js index 5083648e46ebb3..8945f29e813744 100644 --- a/packages/dataviews/src/test/filter-and-sort-data-view.js +++ b/packages/dataviews/src/test/filter-and-sort-data-view.js @@ -254,6 +254,21 @@ describe( 'sorting', () => { } ); it( 'should sort by number', () => { + const { data: result } = filterSortAndPaginate( + data, + { + sort: { field: 'satellites_no_type', direction: 'desc' }, + }, + fields + ); + + expect( result ).toHaveLength( 11 ); + expect( result[ 0 ].title ).toBe( 'Saturn' ); + expect( result[ 1 ].title ).toBe( 'Jupiter' ); + expect( result[ 2 ].title ).toBe( 'Uranus' ); + } ); + + it( 'should sort by type integer', () => { const { data: result } = filterSortAndPaginate( data, { diff --git a/packages/dataviews/src/types.ts b/packages/dataviews/src/types.ts index 37c3efbde5cfb0..f410c18aa43699 100644 --- a/packages/dataviews/src/types.ts +++ b/packages/dataviews/src/types.ts @@ -80,6 +80,11 @@ export type Field< Item > = { */ render?: ComponentType< { item: Item } >; + /** + * Callback used to sort the field. + */ + sort?: ( a: Item, b: Item, direction: SortDirection ) => number; + /** * Whether the field is sortable. */ @@ -124,6 +129,7 @@ export type NormalizedField< Item > = Field< Item > & { label: string; getValue: ( args: { item: Item } ) => any; render: ComponentType< { item: Item } >; + sort: ( a: Item, b: Item, direction: SortDirection ) => number; }; /** diff --git a/packages/edit-site/src/components/post-fields/index.js b/packages/edit-site/src/components/post-fields/index.js index 52030fc70f54b7..9fd8faef96376c 100644 --- a/packages/edit-site/src/components/post-fields/index.js +++ b/packages/edit-site/src/components/post-fields/index.js @@ -242,6 +242,14 @@ function usePostFields( viewType ) { label: name, } ) ) || [], render: PostAuthorField, + sort: ( a, b, direction ) => { + const nameA = a._embedded?.author?.[ 0 ]?.name || ''; + const nameB = b._embedded?.author?.[ 0 ]?.name || ''; + + return direction === 'asc' + ? nameA.localeCompare( nameB ) + : nameB.localeCompare( nameA ); + }, }, { label: __( 'Status' ), diff --git a/packages/edit-site/src/components/post-list/index.js b/packages/edit-site/src/components/post-list/index.js index 68b8461929eb1d..2724abcff714f7 100644 --- a/packages/edit-site/src/components/post-list/index.js +++ b/packages/edit-site/src/components/post-list/index.js @@ -9,7 +9,7 @@ import { import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; import { privateApis as routerPrivateApis } from '@wordpress/router'; import { useSelect, useDispatch } from '@wordpress/data'; -import { DataViews } from '@wordpress/dataviews'; +import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews'; import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { drawerRight } from '@wordpress/icons'; @@ -161,6 +161,8 @@ export default function PostList( { postType } ) { [ history ] ); + const { isLoading: isLoadingFields, fields } = usePostFields( view.type ); + const queryArgs = useMemo( () => { const filters = {}; view.filters.forEach( ( filter ) => { @@ -200,12 +202,25 @@ export default function PostList( { postType } ) { }, [ view ] ); const { records, - isResolving: isLoadingMainEntities, + isResolving: isLoadingData, totalItems, totalPages, } = useEntityRecordsWithPermissions( 'postType', postType, queryArgs ); - const ids = records?.map( ( record ) => getItemId( record ) ) ?? []; + // The REST API sort the authors by ID, but we want to sort them by name. + const data = useMemo( () => { + if ( ! isLoadingFields && view?.sort?.field === 'author' ) { + return filterSortAndPaginate( + records, + { sort: { ...view.sort } }, + fields + ).data; + } + + return records; + }, [ records, fields, isLoadingFields, view?.sort ] ); + + const ids = data?.map( ( record ) => getItemId( record ) ) ?? []; const prevIds = usePrevious( ids ) ?? []; const deletedIds = prevIds.filter( ( id ) => ! ids.includes( id ) ); const postIdWasDeleted = deletedIds.includes( postId ); @@ -263,7 +278,6 @@ export default function PostList( { postType } ) { } ); closeModal(); }; - const { isLoading: isLoadingFields, fields } = usePostFields( view.type ); return (