Copilot commented on code in PR #3305:
URL: https://github.com/apache/apisix-dashboard/pull/3305#discussion_r2896660197


##########
src/components/page/ResourceListPage.tsx:
##########
@@ -0,0 +1,140 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import type { ProColumns } from '@ant-design/pro-components';
+import { ProTable } from '@ant-design/pro-components';
+import type { TablePaginationConfig } from 'antd';
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { AntdConfigProvider } from '@/config/antdConfigProvider';
+
+import PageHeader from './PageHeader';
+import type { ToAddPageBtnProps } from './ToAddPageBtn';
+import { ToAddPageBtn } from './ToAddPageBtn';
+
+interface ResourceListPageProps<T> {
+  titleKey?: string;
+  columns: ProColumns<T>[];
+  queryData: {
+    data?: { list: T[]; total: number } | undefined;
+    isLoading: boolean;
+    pagination: TablePaginationConfig | false;
+    refetch?: () => void;
+  };
+  rowKey: string | ((record: T) => string);
+  addPageTo?: ToAddPageBtnProps['to'];
+  resourceNameKey?: string;
+  emptyText?: React.ReactNode;
+  pageSizeOptions?: number[] | string[];
+  showTotal?: (total: number, range: [number, number]) => React.ReactNode;
+}
+
+function isRecord(val: unknown): val is Record<string, unknown> {
+  return val !== null && typeof val === 'object' && !Array.isArray(val);
+}
+
+const ResourceListPage = <T extends Record<string, unknown>>(
+  props: ResourceListPageProps<T>,
+) => {
+  const {
+    titleKey,
+    columns,
+    queryData,
+    rowKey,
+    addPageTo,
+    resourceNameKey,
+    emptyText,
+    pageSizeOptions,
+    showTotal: customShowTotal,
+  } = props;
+  const { t } = useTranslation();
+  const { data, isLoading, pagination } = queryData;
+
+  const dataSource = useMemo(() => data?.list ?? [], [data]);
+  const total =
+    (pagination !== false ? pagination?.total : undefined) ?? data?.total ?? 0;
+
+  const paginationConfig = useMemo(() => {
+    if (pagination === false) return false;
+
+    return {
+      current: pagination?.current ?? 1,
+      pageSize: pagination?.pageSize ?? 10,
+      total,
+      showSizeChanger: true,
+      pageSizeOptions: pageSizeOptions ?? [10, 20, 50, 100],
+      hideOnSinglePage: false,
+      onChange: pagination?.onChange,
+      ...(customShowTotal ? { showTotal: customShowTotal } : {}),
+    };
+  }, [pagination, total, pageSizeOptions, customShowTotal]);
+
+  const resolvedRowKey = useMemo(
+    () =>
+      typeof rowKey === 'function'
+        ? rowKey
+        : (record: T) => {
+          // APISIX list items wrap data in a `value` property
+          const raw = record['value'];
+          const value = isRecord(raw) ? raw : undefined;
+          return String(value?.[rowKey] ?? record[rowKey] ?? '');

Review Comment:
   The `resolvedRowKey` fallback returns an empty string when the requested key 
is missing (`String(... ?? '')`). This can lead to duplicate React keys (all 
rows share "") and unstable table rendering. Since APISIX list items include a 
stable `key` field on the wrapper object, consider falling back to `record.key` 
(or another guaranteed unique field) instead of `''` when neither 
`value[rowKey]` nor `record[rowKey]` exists.



##########
e2e/utils/req.ts:
##########
@@ -27,27 +27,49 @@ export const getPlaywrightRequestAdapter = (
   ctx: APIRequestContext
 ): AxiosAdapter => {
   return async (config) => {
-    const { url, data, baseURL } = config;
+    const { url, data, baseURL, method } = config;
     if (typeof url === 'undefined') {
       throw new Error('Need to provide a url');
     }
 
     type Payload = Parameters<APIRequestContext['fetch']>[1];
     const payload: Payload = {
       headers: config.headers,
-      method: config.method,
-      failOnStatusCode: true,
+      method,
+      failOnStatusCode: false,
       data,
     };
     const urlWithBase = `${baseURL}${url}`;
     const res = await ctx.fetch(urlWithBase, payload);
+    const status = res.status();
+
+    // Idempotent DELETE: Treat 404 as 200 OK
+    if (method?.toLowerCase() === 'delete' && status === 404) {
+      console.warn(`[e2eReq] Ignored 404 on DELETE for ${urlWithBase}, 
treating as 200 OK.`);
+      return {
+        data: {},
+        status: 200,
+        statusText: 'OK',
+        headers: {},
+        config,
+      };
+    }
 
     try {
+      let responseData = {};
+      try {
+        responseData = await res.json();
+      } catch {
+        // ignore JSON parse errors on empty or text responses
+      }
+      if (config.validateStatus && !config.validateStatus(status)) {
+        throw new Error(`Request failed with status code ${status}`);
+      }

Review Comment:
   The manual `validateStatus` check will almost always run (Axios populates 
`config.validateStatus` from defaults) and throws a plain `Error` for non-2xx 
responses. This bypasses Axios's normal `settle()` behavior, so callers won’t 
receive an `AxiosError` with `response/status` attached, making failures harder 
to debug and potentially breaking code that relies on AxiosError shape. 
Recommend removing this manual check and letting Axios handle status validation 
based on `validateStatus`, while keeping `failOnStatusCode: false` in 
Playwright fetch.
   ```suggestion
   
   ```



##########
src/types/schema/pageSearch.ts:
##########
@@ -19,16 +19,14 @@ import { z } from 'zod';
 
 export const pageSearchSchema = z
   .object({
-    page: z
-      .union([z.string(), z.number()])
-      .optional()
-      .default(1)
-      .transform((val) => (val ? Number(val) : 1)),
-    page_size: z
-      .union([z.string(), z.number()])
-      .optional()
-      .default(10)
-      .transform((val) => (val ? Number(val) : 10)),
+    page: z.preprocess(
+      (val) => (val === undefined || val === null || val === '' ? undefined : 
Number(val)),
+      z.number().int().min(1).optional().default(1)
+    ),

Review Comment:
   `pageSearchSchema` now enforces `int().min(1)` and will throw on `page=0`, 
negative values, or non-numeric strings (because `Number(val)` becomes `NaN`). 
Since this schema is used by TanStack Router `validateSearch` and also 
re-parsed in `useTablePagination`, malformed URLs can now hard-fail the route 
instead of falling back to defaults. To preserve prior behavior, consider 
coercing invalid/NaN/<=0 values to `undefined` in the preprocess (so 
`.default(1)` applies) rather than rejecting them (apply the same approach to 
`page_size`).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to