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

Sort-Desc not working for Album/Artist Name #8

Open
IanMayo opened this issue Dec 6, 2022 · 7 comments
Open

Sort-Desc not working for Album/Artist Name #8

IanMayo opened this issue Dec 6, 2022 · 7 comments
Assignees

Comments

@IanMayo
Copy link
Contributor

IanMayo commented Dec 6, 2022

I'm in the Albums listing. I can sort by artist name:
image

Note, in the above it did look like they were sorted by AlbumId ascending, but id: 34 demonstrates that's not what is happening.

But, if I try to reverse that sort, the items are sorted by AlbumId descending, they are in reverse AlbumId order:
image

@AbegaM
Copy link
Collaborator

AbegaM commented Dec 6, 2022

I just tested it and when the Artist Name sort button is clicked react admin is passing ArtistId instead of Name so the provider is sorting the items by ArtistId, i think this is happening because i am using the to fetch the Artists Name from the /artists API

  export function AlbumList() {
  return (
    <List>
      <Datagrid rowClick="show">
        <TextField source="id" />
        <TextField source="Title" />

        <ReferenceField
          label="Artist Name"
          source="ArtistId"
          reference="artists"
          link="show"
        >
          <TextField source="Name" />
        </ReferenceField>

        <EditButton />
      </Datagrid>
    </List>
  );
}

I will find a solution

@AbegaM
Copy link
Collaborator

AbegaM commented Dec 8, 2022

Sorting albums by Artist name

I am using the list component to show a list of albums.

export function AlbumList() {
  return (
    <List>
      <Datagrid rowClick="show">
        <TextField source="id" />
        <TextField source="Title" />
        <ReferenceField
          label="Artist Name"
          source="ArtistId"
          reference="artists"
          link="show"
          sortBy="Name"
        >
          <TextField source="Name" />
        </ReferenceField>
        <EditButton />
      </Datagrid>
    </List>
  );
}

This component makes two API calls,

  1. The first API call will be sent to the /albums API and it will trigger the getList provider

    GET localhost:8000/api/tables/albums/rows
    
    //API response
    {
      "data": [
        {
          "AlbumId": 1,
          "Title": "For Those About To Rock We Salute You",
          "ArtistId": 3
        },
        {
          "AlbumId": 2,
          "Title": "Balls to the Wall",
          "ArtistId": 2
        },
       ]
    }
  2. The second API call will be sent by the <RererenceField /> component to the /artists API and the getMany provider will be triggered.

    localhost:8000/api/tables/artists/rows/1
    //API response
    {
      "data": [
        {
          "ArtistId": 1,
          "Name": "AC/DC"
        }
      ]
    }

The problem

The issue is that, React admin allows sorting only in the getList provider, and if I want to sort the items by their Artist Name i have to add a sortBy prop in the <ReferenceField /> component

<ReferenceField
    label="Artist Name"
    source="ArtistId"
    reference="artists"
    link="show"
    sortBy="Name"
  >
      <TextField source="Name" />
</ReferenceField>

But adding the sortBy prop will pass the Name to the getList provider and since soul doesn't see a Name property in the response of the request, it will automatically throw an error

//THERE IS NO NAME KEY IN THE API RESPONSE
{
  "data": [
    {
      "AlbumId": 1,
      "Title": "For Those About To Rock We Salute You",
      "ArtistId": 3
    },
   ]
}

But if i pass the _extend query string to the /albums API, soul will return this kind of data, and passing the Name prop in the ReferenceField will not throw an error

GET localhost:8000/api/tables/albums/rows?_extend=ArtistId
{
  "data": [
    {
      "AlbumId": 1,
      "Title": "For Those About To Rock We Salute You",
      "ArtistId": 3,
      "ArtistId_data": {
        "ArtistId": 3,
        "Name": "Aerosmith"
      }
    },
    {
      "AlbumId": 2,
      "Title": "Balls to the Wall",
      "ArtistId": 2,
      "ArtistId_data": {
        "ArtistId": 2,
        "Name": "Accept"
      }
    },
  ]
}
  

The main problem here is that i can't customize the getList provider to add the _extend key

getList: (resource: string, params: any) => {
    const { page, perPage } = params.pagination;
    let { field, order } = params.sort
    
    const query = {
      _page: page,
      _limit: perPage,
      _ordering: ordering,
      _filters: filter ? filter : undefined,
      _extend: 'ArtistId'   //
    };

    const url = `${apiUrl}/${resource}/rows?${stringify(query)}`;

    return axios.get(url).then((response) => {
       //API response
    )}
  },

@IanMayo
Copy link
Contributor Author

IanMayo commented Dec 8, 2022

The main problem here is that i can't customize the getList provider to add the _extend key

What can't you customize the getList provider?

@AbegaM
Copy link
Collaborator

AbegaM commented Dec 8, 2022

Sorry for my language choice, it might be customized, what i meant to say was, i couldn't figure out how to customize it

The issue is that the getList provider is used by many components so i can't manually set a value for the _extend query string like this

const { page, perPage } = params.pagination;
let { field, order } = params.sort
    
const query = {
      _page: page,
      _limit: perPage,
      _ordering: ordering,
      _filters: filter ? filter : undefined,
      
      _extend: 'ArtistId'   // manually add ArtistId
    };

const url = `${apiUrl}/${resource}/rows?${stringify(query)}`;

This will throw a Foreign key not found error for the other components. when the sort buttons are clicked React admin will pass the clicked key via the params.sort object, i can manually write this kind of code, but I i thought this wouldn't be the best way.

getList: (resource: string, params: any) => {
    const { page, perPage } = params.pagination;
    let { field, order } = params.sort
    
    //manually add an _extend key 
    let _extend = undefined
    if(resource === "albums") {
      _extend = "ArtistId"
    }
    
    const query = {
      _page: page,
      _limit: perPage,
      _ordering: ordering,
      _filters: filter ? filter : undefined,
      _extend  //
    };

    const url = `${apiUrl}/${resource}/rows?${stringify(query)}`;

    return axios.get(url).then((response) => {
       //API response
    )}
  },

@IanMayo
Copy link
Contributor Author

IanMayo commented Dec 8, 2022

Sure. I'm no JS expert but this could be it.

  1. Generate query, with extend empty.
  2. then do:
if (resource === "albums") {
  query._extend = "ArtistId"
}

@AbegaM
Copy link
Collaborator

AbegaM commented Dec 8, 2022

ok thank you. setting up the _extend to empty will actually throw an error if it is sent to the backend, i have done something like this

    let extend = undefined 
    if (resource === "albums") {
       extend = "ArtistId"
    }

    const query = {
      _page: page,
      _limit: perPage,
      _ordering: ordering,
      _filters: filter ? filter : undefined, 
      _extend: extend
    };

@AbegaM
Copy link
Collaborator

AbegaM commented Dec 8, 2022

Update

All of the features are working well, except for the filtering feature, i am facing the same problem as the sorting issue

The TrackList component makes around four API calls

  1. The TrackList component makes API call to the /tracks API to get all of the tracks
  2. The first <ReferenceField /> component makes API call to the /albums API
  3. The second <ReferenceField /> component makes API call to the /genres API
  4. The third <ReferenceField /> component makes API call to the /media_types API
const trackFilters = [
  <TextInput label="Id" source="TrackId" alwaysOn />,
  <TextInput label="Name" source="Name" />,
  <TextInput label="Album Name" source="AlbumId" />,
  <TextInput label="Genre" source="GenreId" />,
  <TextInput label="Media type" source="MediaTypeId" />,
];

export function TrackList() {
  return (
    <List filters={trackFilters}>
      <Datagrid rowClick="show">
        <TextField source="id" />
        <TextField source="Name" />

        <ReferenceField
          label="Album Name"
          source="AlbumId"
          reference="albums"
          link="show"
        >
          <TextField source="Title" />
        </ReferenceField>

        <ReferenceField
          label="Genre"
          source="GenreId"
          reference="genres"
          link="show"
        >
          <TextField source="Name" />
        </ReferenceField>

        <ReferenceField
          label="Media type"
          source="MediaTypeId"
          reference="media_types"
          link="show"
        >
          <TextField source="Name" />
        </ReferenceField>

        <TextField source="Composer" />
        <TextField source="Milliseconds" />
        <TextField source="Bytes" />
        <TextField source="UnitPrice" />
        <EditButton />
      </Datagrid>
    </List>
  );
}

The problem

The only provider which will perform filtering is the getList provider and the filter will be passed to the /tracks API, and this is the response of the /tracks API

//GET: localhost:8000/api/tables/tracks/rows
{
  "data": [
    {
      "TrackId": 1,
      "Name": "For Those About To Rock (We Salute You)",
      "AlbumId": 1,
      "MediaTypeId": 1,
      "GenreId": 1,
      "Composer": "Angus Young, Malcolm Young, Brian Johnson",
      "Milliseconds": 3,
      "Bytes": 11170334,
      "UnitPrice": 0.99
    },
   ]
}

I have used the _extend query string to fetch additional fields for sorting, and i get this kind of data

//localhost:8000/api/tables/tracks/rows?_extend=AlbumId,GenreId,MediaTypeId
{
  "data": [
    {
      "TrackId": 1,
      "Name": "For Those About To Rock (We Salute You)",
      "AlbumId": 1,
      "MediaTypeId": 1,
      "GenreId": 1,
      "Composer": "Angus Young, Malcolm Young, Brian Johnson",
      "Milliseconds": 3,
      "Bytes": 11170334,
      "UnitPrice": 0.99,
      "AlbumId_data": {
        "AlbumId": 1,
        "Title": "For Those About To Rock We Salute You",
        "ArtistId": 3
      },
      "GenreId_data": {
        "GenreId": 1,
        "Name": "Rock 2"
      },
      "MediaTypeId_data": {
        "MediaTypeId": 1,
        "Name": "MPEG audio file"
      }
    },
   ]
}

So for example, if the user wants to filter the tracks by their AlbumName, React admin would pass either the AlbumId or the Title property in the params.filter object, using the AlbumId property will not filter the tracks by AlbumName but we can use the Title for filtering, but the issue is that Soul throws an error when the client tries to filter tracks by Title

The same approach was working fine for the sorting but not for the filtering.

I will keep working on it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants