next-drupal icon indicating copy to clipboard operation
next-drupal copied to clipboard

getMenu function fails to fetch menu.

Open Danishkhurshid opened this issue 1 year ago • 5 comments

Package containing the bug

next-drupal (NPM package)

Describe the bug

I am getting an error Error: Resource of type 'menu_items' not found. on trying to fetch menus with getMenu.

After debugging i see that buildEndpoint generates /jsonapi as an endpoint to fetch menus rather than /jsonapi/menu_items/{menu}

Fix: getMenu should call the buildEndpoint as below by concatenating the path rather than passing menu_items as a resourceType.

   const endpoint = await this.buildEndpoint({
      locale:
        options?.locale !== options?.defaultLocale ? options.locale : undefined,
      path: `/menu_items/${menuName}`,
      searchParams: options.params,
    })

Danishkhurshid avatar May 02 '24 08:05 Danishkhurshid

I would like to help and provide a fix/PR for this but I would like, if possible, feedback on what's the best approach:

  1. Call the endpoint without the resourceType parameter? Like presented by @Danishkhurshid
  2. Add the resource to JSON:API Menu Items module? And maybe list all available menus?

vonloxx avatar Feb 17 '25 12:02 vonloxx

Has there been any progress here? I am trying to upgrade to next drupal v 2.0.0 and this is a potential blocker.

Zionknight-crypto avatar Apr 01 '25 14:04 Zionknight-crypto

Running into the same issue as other folks here, specifically in my pages router usage (but haven't tested in app router yet). I've got a fix for my own lib/drupal.ts file, should others find it useful. Basically just extending the base NextDrupalPages implementation to fix the issue in getMenu method.

import { DrupalMenuItem, DrupalMenuItemId, JsonApiOptions, JsonApiWithCacheOptions, JsonApiWithNextFetchOptions, NextDrupalPages } from 'next-drupal';

/**
 * Recreate DrupalMenuTree from next-drupal (this is not exported from the library)
 * https://github.com/chapter-three/next-drupal/blob/main/packages/next-drupal/src/menu-tree.ts
 */
class DrupalMenuTree<
  T extends {
    id: DrupalMenuItemId;
    parent: DrupalMenuItemId;
    items?: T[];
  } = DrupalMenuItem,
> extends Array {
  parentId: DrupalMenuItemId;
  depth: number;

  constructor(
    menuItems: T[],
    parentId: DrupalMenuItemId = '',
    depth: number = 1,
  ) {
    super();

    this.parentId = parentId;
    this.depth = depth;

    if (menuItems?.length) {
      this.build(menuItems, parentId);
    }
  }

  build(menuItems: T[], parentId: DrupalMenuItemId) {
    // Find the children of the specified parent.
    const children = menuItems.filter(
      (menuItem) => menuItem?.parent === parentId,
    );

    // Add each child to this Array.
    for (const menuItem of children) {
      const subtree = new DrupalMenuTree<T>(
        menuItems,
        menuItem.id,
        this.depth + 1,
      );

      this.push({
        ...menuItem,
        items: subtree.length ? subtree : undefined,
      });
    }
  }
}

/**
 * Extend the DrupalClient class to override the getMenu method.
 * This is because the getMenu method in next-drupal is not working as expected.
 * https://github.com/chapter-three/next-drupal/issues/752
 */
class CustomDrupalClient extends NextDrupalPages {
  async getMenu<T = DrupalMenuItem>(
    menuName: string,
    options?: JsonApiOptions &
      JsonApiWithCacheOptions &
      JsonApiWithNextFetchOptions,
  ): Promise<{
    items: T[];
    tree: T[];
  }> {
    options = {
      withAuth: this.withAuth,
      deserialize: true,
      params: {},
      withCache: false,
      ...options,
    };

    const endpoint = await this.buildEndpoint({
      locale:
        options?.locale !== options?.defaultLocale ? options.locale : undefined,
      path: `menu_items/${menuName}`,
      searchParams: options.params,
    });

    this.debug(`Fetching menu items for ${menuName}.`);

    const response = await this.fetch(endpoint, {
      withAuth: options.withAuth,
      next: options.next,
      cache: options.cache,
    });

    await this.throwIfJsonErrors(response, 'Error while fetching menu items: ');

    const data = await response.json();

    const items = options.deserialize ? this.deserialize(data) : data;

    const tree = new DrupalMenuTree(items);

    const menu = {
      items,
      tree: tree.length ? tree : undefined,
    };

    return menu as { items: T[]; tree: T[] };
  }
}

export const drupal = new CustomDrupalClient(
  process.env.NEXT_PUBLIC_DRUPAL_BASE_URL || '',
  {
    auth: {
      clientId: process.env.DRUPAL_CLIENT_ID || '',
      clientSecret: process.env.DRUPAL_CLIENT_SECRET || '',
    },
    frontPage: process.env.DRUPAL_FRONT_PAGE || '/home',
  },
);

nuuou avatar Apr 01 '25 18:04 nuuou

Thank you very much for the share @Nuuou ! My colleague @vermario and I will look into this tomorrow to see if we can contribute as well.

Zionknight-crypto avatar Apr 02 '25 06:04 Zionknight-crypto

@vonloxx menu_items is not an entity and therefore is not exposed in the /jsonapi index. Making menu_items available as a JSON:API resource like other entities seems like a lot of work, whereas we can address this more straightforwardly on the next-drupal side.

FYI: I've created a PR (#866) with the proposed solution.

bojanbogdanovic avatar Jul 09 '25 12:07 bojanbogdanovic