Skip to content

Fixes #22399: Add persona customization for service details page#27318

Open
RajdeepKushwaha5 wants to merge 22 commits intoopen-metadata:mainfrom
RajdeepKushwaha5:feature/22399-service-persona-customization
Open

Fixes #22399: Add persona customization for service details page#27318
RajdeepKushwaha5 wants to merge 22 commits intoopen-metadata:mainfrom
RajdeepKushwaha5:feature/22399-service-persona-customization

Conversation

@RajdeepKushwaha5
Copy link
Copy Markdown
Contributor

Describe your changes:

Fixes #22399

What changes did I make?

Added persona-based UI customization support for the Service Details Page, following the same pattern used by Table, Dashboard, Topic, Pipeline, and other entity detail pages.

Changes breakdown:

  • Schema: Added "Service" to PageType enum in page.json (+ generated TS types)
  • ServiceDetailsClassBase (new): Defines 7 tab IDs, default widget layout (Description + ServiceEntityTable on left, DataProducts/Tags/GlossaryTerms/CustomProperties on right), widget heights, dummy data, and common widget list
  • ServiceEntityTable widget (new): Self-contained widget component for displaying child entities (databases, topics, dashboards, etc.) with search, pagination, and show-deleted toggle
  • ServiceDetailsPage.util.tsx (new): Widget rendering dispatch — maps widget keys to components
  • ServiceDetailPage.constants.ts (new): Dummy data for the customization editor preview
  • CustomizePageUtils.ts: Added PageType.Service cases to all 6 switch functions (getDefaultTabs, getDefaultWidgetForTab, getCustomizableWidgetByPage, getDummyDataByPage, getWidgetsFromKey, getWidgetHeight)
  • Customize.constants.ts: Added EntityType.DATABASE_SERVICE to CustomizeEntityType union and ENTITY_PAGE_TYPE_MAP
  • CustomizeDetailPage.interface.ts: Added PageType.Service → EntityType.DATABASE_SERVICE mapping
  • PersonaUtils.ts: Added Service card with icon to the Customize UI persona page
  • CustomizablePage.tsx: Added PageType.Service routing to CustomizeDetailsPage
  • ServiceDetailsPage.tsx: Integrated GenericProvider, GenericTab, and useCustomPages(PageType.Service) to consume persona customization
  • AddDetailsPageWidgetModal.tsx: Fixed widget width scaling — WidgetWidths enum values (1/2/3) are designed for the 3-column landing page but detail pages use an 8-column grid, causing widgets added via "+Add" to render too narrow. Added a scale factor (maxGridSizeSupport / landingPageMaxGridSize) so small→3, medium→5, large→8 columns

Why did I make them?

The Service Details Page was the only major entity page without persona customization support. This blocked users from customizing the layout/widgets shown on service pages per persona.

How did I test?

  • All 50 existing ServiceDetailsPage.test.tsx unit tests pass
  • ESLint and TypeScript: 0 errors across all 16 changed files
  • Manual testing: navigated to Settings → Personas → Customize UI → Service card → customization editor, added/removed/resized widgets, saved layout, verified on actual service detail pages

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.
  • The issue properly describes why the new feature is needed, what's the goal, and how we are building it. Any discussion
    or decision-making process is reflected in the issue.
  • I have updated the documentation.
  • I have added tests around the new logic.

Copilot AI review requested due to automatic review settings April 13, 2026 10:59
@RajdeepKushwaha5 RajdeepKushwaha5 requested a review from a team as a code owner April 13, 2026 10:59
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RajdeepKushwaha5
Copy link
Copy Markdown
Contributor Author

persona.mp4

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds persona-based UI customization support to the Service Details page, aligning it with other customizable entity detail pages (Table/Dashboard/Topic/etc.) by introducing a Service page type, default tab/layout definitions, and a new widget for listing service child entities.

Changes:

  • Extends UI customization schema/types to support PageType.Service and wires Service pages into customization routing and persona UI.
  • Introduces ServiceDetailsClassBase + widget-dispatch utility to provide default tabs/layout, widget heights, and dummy data for Service customization.
  • Adds ServiceEntityTable widget and updates ServiceDetailsPage to render the DETAILS tab via GenericProvider/GenericTab with useCustomPages(PageType.Service).

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/ServiceDetailsPage/ServiceDetailsPage.util.tsx Service widget rendering dispatch (ServiceEntityTable vs CommonWidgets).
openmetadata-ui/src/main/resources/ui/src/utils/ServiceDetailsPage/ServiceDetailsClassBase.ts Defines Service default tabs/layout, widget list, heights, and dummy data.
openmetadata-ui/src/main/resources/ui/src/utils/Persona/PersonaUtils.ts Adds Service option/card in persona customization UI (icon + menu options).
openmetadata-ui/src/main/resources/ui/src/utils/CustomizePage/CustomizePageUtils.ts Adds PageType.Service handling across customization utility switches.
openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx Integrates GenericProvider/GenericTab + useCustomPages for Service DETAILS tab.
openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.test.tsx Updates unit tests to mock new customization hooks/components.
openmetadata-ui/src/main/resources/ui/src/pages/CustomizeDetailsPage/CustomizeDetailPage.interface.ts Maps PageType.Service to an entity type for customization editor.
openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx Routes PageType.Service to the CustomizeDetailsPage editor.
openmetadata-ui/src/main/resources/ui/src/generated/system/ui/uiCustomization.ts Generated enum update to include PageType.Service.
openmetadata-ui/src/main/resources/ui/src/generated/system/ui/page.ts Generated enum update to include PageType.Service.
openmetadata-ui/src/main/resources/ui/src/enums/CustomizeDetailPage.enum.ts Adds new widget key SERVICE_ENTITY_TABLE.
openmetadata-ui/src/main/resources/ui/src/constants/ServiceDetailPage.constants.ts Provides dummy service data for customization editor previews.
openmetadata-ui/src/main/resources/ui/src/constants/Customize.constants.ts Extends CustomizeEntityType + entity→page mapping to include Service.
openmetadata-ui/src/main/resources/ui/src/components/Service/ServiceEntityTable/ServiceEntityTable.tsx New widget rendering searchable/paginated child-entity table for services.
openmetadata-ui/src/main/resources/ui/src/components/MyData/CustomizableComponents/AddDetailsPageWidgetModal/AddDetailsPageWidgetModal.tsx Scales widget widths for detail-page grids vs landing-page grid sizing.
openmetadata-spec/src/main/resources/json/schema/system/ui/page.json Adds "Service" to PageType enum in schema.

Comment on lines +1987 to +1992
<GenericProvider<ServicesType>
customizedPage={customizedPage}
data={serviceDetails}
permissions={servicePermission}
type={entityType as CustomizeEntityType}
onUpdate={saveUpdatedServiceData}>
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericProvider is being given type={entityType as CustomizeEntityType}, but CustomizeEntityType currently only includes EntityType.DATABASE_SERVICE. For non-database service categories (messaging, dashboard, pipeline, drive, etc.), ENTITY_PAGE_TYPE_MAP[type] becomes undefined, and GenericProvider falls back to an empty default layout when customizedPage is null—making the DETAILS tab render blank for personas without saved customization. Expand CustomizeEntityType/ENTITY_PAGE_TYPE_MAP to include all *Service entity types used by getEntityTypeFromServiceCategory, and remove the unsafe cast so default layouts work across all service categories.

Copilot uses AI. Check for mistakes.
Comment thread openmetadata-ui/src/main/resources/ui/src/constants/Customize.constants.ts Outdated
Comment on lines 117 to 122
[EntityType.DIRECTORY]: PageType.Directory,
[EntityType.FILE]: PageType.File,
[EntityType.SPREADSHEET]: PageType.Spreadsheet,
[EntityType.WORKSHEET]: PageType.Worksheet,
[EntityType.DATABASE_SERVICE]: PageType.Service,
};
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ENTITY_PAGE_TYPE_MAP maps only EntityType.DATABASE_SERVICE to PageType.Service. If GenericProvider is passed another service entity type (messaging, dashboard, pipeline, etc.), ENTITY_PAGE_TYPE_MAP[type] becomes undefined and default layouts won’t load. Map all service entity types that share the Service details page to PageType.Service.

Copilot uses AI. Check for mistakes.
Comment on lines +183 to +194
public getCommonWidgetList() {
return [
DESCRIPTION_WIDGET,
{
fullyQualifiedName: DetailPageWidgetKeys.SERVICE_ENTITY_TABLE,
name: i18n.t('label.entity-detail-plural', {
entity: i18n.t('label.service'),
}),
data: {
gridSizes: ['large'] as GridSizes[],
},
},
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new SERVICE_ENTITY_TABLE widget key is added to the Service detail page widget list, but the Customize Details editor preview renders widgets via WIDGET_COMPONENTS (see utils/GenericWidget/GenericWidgetUtils.tsx). Since SERVICE_ENTITY_TABLE isn’t registered there, the customization editor will show the widget name only (fallback), not the actual table preview. Add a WIDGET_COMPONENTS entry for DetailPageWidgetKeys.SERVICE_ENTITY_TABLE (and render ServiceEntityTable in customization mode) so the editor preview matches the real page.

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +105
const res = await searchQuery({
pageNumber,
pageSize,
searchIndex,
query: searchValue ? `*${searchValue}*` : '',
queryFilter: buildSchemaQueryFilter(
'service.fullyQualifiedName.keyword',
decodedServiceFQN,
searchValue
),
includeDeleted: showDeleted,
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetchEntities passes a non-empty query (*${searchValue}*) while also applying a queryFilter built by buildSchemaQueryFilter (which already adds wildcard should-queries). Existing service search (ServiceMainTabContent.searchService) and similar table widgets use query: '' and rely on queryFilter for wildcard matching. Using both can unintentionally over-filter results and diverge from existing behavior; align this with the established pattern by keeping query: '' and relying on the query filter for search matching.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 13, 2026 11:24
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 3 comments.

@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1431

  • The guard in this effect now returns early when activeTab === EntityTabs.DETAILS, which means getOtherDetails(...) runs on every other tab (Connection/Agents/etc.) and never runs on the DETAILS tab. This looks inverted compared to the intent (fetching child-entity lists/counts for the details tab) and can cause redundant REST calls while navigating non-details tabs. Consider either removing this legacy fetch entirely (if ServiceEntityTable owns details data) or restoring the condition so this fetch only happens for the tab that still needs it.
  useEffect(() => {
    // ServiceEntityTable handles its own data fetching for the DETAILS tab
    // (child entities like Databases, Topics, etc.), so skip the legacy
    // REST API call when activeTab is EntityTabs.DETAILS.
    if (searchValue || activeTab === EntityTabs.DETAILS) {
      return;
    }
    const { cursorType, cursorValue } = pagingInfo?.pagingCursor ?? {};
    getOtherDetails({
      limit: pageSize,
      ...(cursorType && { [cursorType]: cursorValue }),
    });
    if (isInitialPaginationLoadRef.current) {

@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.

@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1482

  • This effect still calls getOtherDetails(...) for non-DETAILS tabs, but the DETAILS tab is now rendered via GenericTab/ServiceEntityTable and the legacy data/paging state is no longer used in the UI. This results in unnecessary REST calls/state updates whenever users are on other tabs. Consider removing this legacy fetch (and the related paging/state) now that the DETAILS tab owns its own data fetching.
  useEffect(() => {
    // ServiceEntityTable widget handles its own data fetching for the
    // DETAILS tab (the tab showing child entities: Databases, Topics,
    // Pipelines, etc.), so skip the legacy REST API call on that tab.
    if (searchValue || activeTab === EntityTabs.DETAILS) {
      return;
    }
    const { cursorType, cursorValue } = pagingInfo?.pagingCursor ?? {};
    getOtherDetails({
      limit: pageSize,
      ...(cursorType && { [cursorType]: cursorValue }),
    });
    if (isInitialPaginationLoadRef.current) {
      isInitialPaginationLoadRef.current = false;
    }
  }, [
    showDeleted,
    deleted,
    pageSize,
    searchValue,
    pagingInfo?.pagingCursor,
    activeTab,
    serviceCategory,
  ]);

Comment on lines 276 to 286
const [serviceDetails, setServiceDetails] = useState<ServicesType>(
{} as ServicesType
);
const [data, setData] = useState<Array<ServicePageData>>([]);
const [_data, setData] = useState<Array<ServicePageData>>([]);
const [files, setFiles] = useState<Array<File>>([]);
const [spreadsheets, setSpreadsheets] = useState<Array<Spreadsheet>>([]);
const [isLoading, setIsLoading] = useState(!isOpenMetadataService);
const [isIngestionPipelineLoading, setIsIngestionPipelineLoading] =
useState(false);
const [isServiceLoading, setIsServiceLoading] = useState(true);
const [_isServiceLoading, setIsServiceLoading] = useState(true);
const [isFilesLoading, setIsFilesLoading] = useState(true);
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _data and _isServiceLoading state values are never read anywhere in this component (only their setters are used). This looks like leftover state from the removed ServiceMainTabContent flow; consider removing these state variables (and any related updates) to avoid dead code and confusion.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1470

  • The useEffect that calls getOtherDetails() currently runs for every tab except EntityTabs.DETAILS (it returns early when activeTab === EntityTabs.DETAILS). Since the DETAILS tab has been migrated to ServiceEntityTable/GenericTab, this legacy REST fetch looks redundant and now triggers extra network calls + state updates on tabs like INSIGHTS/CONNECTION (and potentially on metadata/security services too). Consider removing this effect entirely, or gating it so getOtherDetails() only runs when it is still needed (e.g., only when activeTab === EntityTabs.DETAILS for any remaining legacy rendering paths).
  useEffect(() => {
    // ServiceEntityTable widget handles its own data fetching for the
    // DETAILS tab (the tab showing child entities: Databases, Topics,
    // Pipelines, etc.), so skip the legacy REST API call on that tab.
    if (searchValue || activeTab === EntityTabs.DETAILS) {
      return;
    }
    const { cursorType, cursorValue } = pagingInfo?.pagingCursor ?? {};
    getOtherDetails({
      limit: pageSize,
      ...(cursorType && { [cursorType]: cursorValue }),
    });

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 21 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1482

  • The useEffect that calls getOtherDetails now runs on any change when activeTab is not DETAILS and searchValue is empty, but the fetched data/paging/isServiceLoading state is no longer used in the UI (those variables were renamed to _data, _paging, _isServiceLoading). This causes unnecessary background REST calls and state updates. Remove this legacy prefetch path (and the now-unused state) or gate it so it only runs when some rendered tab actually consumes the results.
  useEffect(() => {
    // ServiceEntityTable widget handles its own data fetching for the
    // DETAILS tab (the tab showing child entities: Databases, Topics,
    // Pipelines, etc.), so skip the legacy REST API call on that tab.
    if (searchValue || activeTab === EntityTabs.DETAILS) {
      return;
    }
    const { cursorType, cursorValue } = pagingInfo?.pagingCursor ?? {};
    getOtherDetails({
      limit: pageSize,
      ...(cursorType && { [cursorType]: cursorValue }),
    });
    if (isInitialPaginationLoadRef.current) {
      isInitialPaginationLoadRef.current = false;
    }
  }, [
    showDeleted,
    deleted,
    pageSize,
    searchValue,
    pagingInfo?.pagingCursor,
    activeTab,
    serviceCategory,
  ]);

Comment on lines 1772 to 1810
const tabs: TabsProps['items'] = useMemo(() => {
const tabs = [];
const ownerIds = serviceDetails?.owners?.map((owner) => owner.id) ?? [];
const userOwnsService = ownerIds.includes(currentUser?.id ?? '');
const userInOwnerTeam = Boolean(
currentUser?.teams?.some((team) => ownerIds.includes(team.id))
);

const showIngestionTab = userInOwnerTeam || userOwnsService || isAdminUser;

// Create extension context for plugins
const extensionContext: PluginEntityDetailsContext = {
serviceCategory: serviceCategory as ServiceCategory,
serviceDetails,
};

if (!isMetadataService && !isSecurityService) {
tabs.push(
{
name: t('label.insight-plural'),
key: EntityTabs.INSIGHTS,
children: (
<ServiceInsightsTab
collateAIagentsList={collateAgentsList}
ingestionPipelines={ingestionPipelines}
isCollateAIagentsLoading={isCollateAgentLoading}
isIngestionPipelineLoading={isIngestionPipelineLoading}
serviceDetails={serviceDetails}
workflowStatesData={workflowStatesData}
/>
),
},
{
name: getCountLabel(serviceCategory),
key: getCountLabel(serviceCategory).toLowerCase(),
count: paging.total,
children: (
<ServiceMainTabContent
currentPage={currentPage}
data={data}
isServiceLoading={isServiceLoading}
paging={paging}
pagingInfo={pagingInfo}
saveUpdatedServiceData={saveUpdatedServiceData}
serviceDetails={serviceDetails}
serviceName={serviceCategory}
servicePermission={servicePermission}
setFilters={setFilters}
setIsServiceLoading={setIsServiceLoading}
showDeleted={showDeleted}
onDataProductUpdate={handleDataProductUpdate}
onDescriptionUpdate={handleDescriptionUpdate}
onShowDeletedChange={handleShowDeleted}
/>
),
key: EntityTabs.DETAILS,
count: serviceEntityCount,
children: <GenericTab type={PageType.Service} />,
}
);
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

customizedPage is fetched and passed into GenericProvider, but the actual Tabs items are still built entirely from local logic and never use customizedPage.tabs (or helpers like getDetailsTabWithNewLabel / getTabLabelMapFromTabs). As a result, persona customization that hides/reorders tabs or adds new tabs won’t be reflected on the Service Details page—only the widget layout for the existing DETAILS tab can change. Update the tabs construction to be driven by customizedPage.tabs (falling back to defaults when null) so new/hidden tabs show up and map to the correct tab children (e.g., custom tabs should render GenericTab too).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 19 out of 21 changed files in this pull request and generated 1 comment.

…ilures

- Fix getDefaultLayout to return empty layout when tab param is undefined
- Use exact text matching in ServiceCustomization test locators
- Update Pagination tests to use search API and /details tab route
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

❌ Playwright Lint Check Failed — ESLint + Prettier + Organise Imports

The following files have style issues that need to be fixed:
playwright/e2e/Features/ServiceCustomization.spec.ts playwright/constant/customizeDetail.ts playwright/e2e/Features/Pagination.spec.ts

Fix locally (fast — only for changed files in the branch):

make ui-checkstyle-playwright-changed

Or to fix all playwright files:

make ui-checkstyle-playwright

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:243

  • pagingInfo is now only destructured into _paging (unused) plus currentPage/handlePageChange/handlePagingChange, but the service children list is rendered by ServiceEntityTable which manages its own paging. Consider removing the unused paging: _paging destructure (and ideally the pagingInfo hook entirely if it’s no longer driving any UI) to avoid leftover state that can confuse future maintenance.
  const {
    paging: _paging,
    currentPage,
    handlePageChange,
    handlePagingChange,
  } = pagingInfo;

);
}, [
currentUser,
currentPage,
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tabs useMemo dependency list includes currentPage, but currentPage no longer appears to be used in the memo body after switching the details tab content to GenericTab. Dropping unused dependencies helps avoid unnecessary recomputation and makes it clearer what actually drives tab rendering.

Suggested change
currentPage,

Copilot uses AI. Check for mistakes.
const {
version: currentVersion,
deleted,
deleted: _deleted,
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted: _deleted is destructured from serviceDetails but never used. It’d be cleaner to omit it from the destructuring to reduce noise (especially since the underscore-prefixed variable is only there to satisfy no-unused-vars).

Suggested change
deleted: _deleted,

Copilot uses AI. Check for mistakes.
Comment on lines 69 to +81
sortBy(widgetsList, 'name')?.map((widget) => {
const scaleFactor = maxGridSizeSupport / LANDING_PAGE_MAX_GRID_SIZE;
const widgetSizeOptions: Array<WidgetSizeInfo> =
widget.data.gridSizes.map((size: GridSizes) => ({
label: (
<span data-testid={`${size}-size-selector`}>
{getWidgetWidthLabelFromKey(toString(size))}
</span>
),
value: WidgetWidths[size],
value: Math.min(
maxGridSizeSupport,
Math.max(1, Math.round(WidgetWidths[size] * scaleFactor))
),
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scaleFactor depends only on maxGridSizeSupport (and the landing-page max grid size), but it’s recomputed inside the widget .map() loop. Consider computing it once outside the loop to reduce repeated work and make the sizing logic easier to follow.

Copilot uses AI. Check for mistakes.
RajdeepKushwaha5 and others added 2 commits April 16, 2026 16:55
…n AddDetailsPageWidgetModal

Moved the landingPageMaxGridSize lookup inside the component so that test suites that mock customizeMyDataPageClassBase without providing this property no longer crash on module import. This fixes CustomizableLandingPageUtils, WidgetCard and CustomiseLandingPageHeader test failures observed in ui-coverage-tests.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 2 comments.

Comment on lines +59 to +66
interface ServiceEntityTableProps {
isCustomizationPage?: boolean;
}

const ServiceEntityTable = ({
isCustomizationPage = false,
}: ServiceEntityTableProps) => {
const routeParams = useParams<{ serviceCategory?: string }>();
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServiceEntityTable is a new component but it doesn’t follow the repo UI file/interface conventions (component file should be *.component.tsx, and prop types/interfaces should live in a *.interface.ts file rather than being declared inline). This makes the new component inconsistent with the expected UI structure and harder to maintain as it grows.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +1182 to +1202
const fetchEntityCount = async () => {
try {
const res = await searchQuery({
pageNumber: 1,
pageSize: 0,
searchIndex: getSearchIndexForService(serviceCategory),
query: '',
queryFilter: buildSchemaQueryFilter(
'service.fullyQualifiedName.keyword',
decodedServiceFQN,
searchValue ? toString(searchValue) : undefined
),
includeDeleted: showDeleted,
trackTotalHits: true,
});
setServiceEntityCount(res.hits.total.value);
} catch {
// count fetch is best-effort
}
};
fetchEntityCount();
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServiceDetailsPage now issues an extra searchQuery request just to compute the DETAILS tab count, while ServiceEntityTable already performs a searchQuery with trackTotalHits: true and gets the same total. This duplicates backend calls on every filter change (search/show-deleted) and can be avoided by plumbing the total hit count from the table fetch back to the parent (e.g., via context/callback) or otherwise sharing the result.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 16, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 63%
63.33% (59355/93717) 43.6% (31465/72151) 46.49% (9439/20303)

- Fix inverted guard in ServiceDetailsClassBase.getDefaultLayout so default layout is returned when tab is undefined or the primary DETAILS tab (flagged by gitar-bot) - Remove unused outer currentPage from pagingInfo destructure and from tabs useMemo deps in ServiceDetailsPage - Remove unused _deleted destructure in ServiceDetailsPage - Hoist scaleFactor computation out of .map() loop in AddDetailsPageWidgetModal - Lint/prettier clean-up across changed files and playwright specs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 2 comments.

Comment on lines +448 to 451
if (key === EntityTabs.DETAILS) {
handlePageChange(INITIAL_PAGING_VALUE, {
cursorType: null,
cursorValue: undefined,
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeTabHandler resets pagination via the local pagingInfo.handlePageChange(...) when switching to the Details tab. Since the Details content is now rendered via GenericTab/ServiceEntityTable (which manages its own usePaging state), this reset only mutates the shared URL params and may not actually reset the table’s internal page state, leading to URL/UI desync. Consider removing this reset here and delegating it to ServiceEntityTable, or lift/persist a single paging state that both parent and widget share.

Copilot uses AI. Check for mistakes.
Comment on lines +1176 to +1189
const fetchEntityCount = async () => {
try {
const res = await searchQuery({
pageNumber: 1,
pageSize: 0,
searchIndex: getSearchIndexForService(serviceCategory),
query: '',
queryFilter: buildSchemaQueryFilter(
'service.fullyQualifiedName.keyword',
decodedServiceFQN,
searchValue ? toString(searchValue) : undefined
),
includeDeleted: showDeleted,
trackTotalHits: true,
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetchEntityCount effect issues an extra /search/query request (pageSize=0) on mount and whenever showDeleted/searchValue change, even though the Details tab’s ServiceEntityTable also runs a search query and already receives hits.total.value. This duplicates work and adds load. Consider deriving serviceEntityCount from the table fetch (e.g., via a callback/context update) or gating this effect to only run when the Details tab is active.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 1 comment.

Comment on lines 507 to +513
jest.mock('../../utils/ServiceUtils', () => ({
getCountLabel: jest.fn().mockReturnValue('Databases'),
getEntityTypeFromServiceCategory: jest.fn().mockReturnValue('database'),
getResourceEntityFromServiceCategory: jest
.fn()
.mockReturnValue('databaseService'),
getSearchIndexForService: jest.fn().mockReturnValue('database_search_index'),
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this test mock, getEntityTypeFromServiceCategory returns 'database', but the real implementation returns an EntityType value (e.g. 'databaseService'). Using a non-realistic value here can mask issues where the entity type is propagated into GenericProvider / customization logic. Consider updating the mock to return EntityType.DATABASE_SERVICE (or the exact string value) to match production behavior.

Copilot uses AI. Check for mistakes.
@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented Apr 20, 2026

Code Review ✅ Approved 12 resolved / 12 findings

Implements persona customization for the service details page, addressing multiple fetch logic inefficiencies, type mapping errors, and test stability issues. No issues found.

✅ 12 resolved
Bug: Non-database service types not mapped in CustomizeEntityType

📄 openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1991 📄 openmetadata-ui/src/main/resources/ui/src/constants/Customize.constants.ts:93-94 📄 openmetadata-ui/src/main/resources/ui/src/constants/Customize.constants.ts:121 📄 openmetadata-ui/src/main/resources/ui/src/utils/ServiceDetailsPage/ServiceDetailsPage.util.tsx:26
ServiceDetailsPage serves ALL service categories (database, messaging, pipeline, dashboard, ML model, storage, search, API, drive), but only EntityType.DATABASE_SERVICE is added to CustomizeEntityType and ENTITY_PAGE_TYPE_MAP. When viewing a messaging service, getEntityTypeFromServiceCategory() returns EntityType.MESSAGING_SERVICE, which is then force-cast via entityType as CustomizeEntityType (line 1991). Inside GenericProvider, ENTITY_PAGE_TYPE_MAP[type] will return undefined for any non-database service, breaking layout resolution and widget rendering.

All service entity types that can reach this page need to be added to CustomizeEntityType and ENTITY_PAGE_TYPE_MAP, or a single canonical service entity type should be resolved before passing it to GenericProvider.

Quality: Misleading comment about which tab skips fetching

📄 openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1420-1421
The comment at line 1420-1421 says 'skip the legacy REST API call when on Entity list tab' but 'Entity list tab' is ambiguous — it could refer to the tab showing child entities (databases/topics/etc.) or to a different tab. Since EntityTabs.DETAILS is the tab key but the visible label is the service-specific count label (e.g., 'Databases'), the comment should clarify that DETAILS tab = the entity-list/child-entities tab.

Bug: Hardcoded DATABASE_SERVICE type breaks non-database services

📄 openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1991
The type prop passed to GenericProvider is hardcoded to EntityType.DATABASE_SERVICE, but the service details page serves all service categories (Messaging, Dashboard, Pipeline, ML Model, Storage, Search, API, etc.). The entityType variable already correctly resolves via getEntityTypeFromServiceCategory(serviceCategory) and can be any of ~11 different entity types.

GenericProvider uses type for:

  • ENTITY_PAGE_TYPE_MAP[type] → page layout config lookup
  • useEntityRules(type) → entity-specific permission rules
  • useChangeSummary(type, ...) → change tracking

All of these will return incorrect results for non-database service types, causing wrong page layouts, wrong permission rules, and wrong change summaries.

Bug: Tab count badge removed for service entities tab

📄 openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceDetailsPage.tsx:1765-1768
The old tab definition included count: paging.total which displayed a count badge (e.g., "Databases (5)") on the service entities tab. The new code removes this entirely because ServiceEntityTable manages its own paging state, so the parent's paging.total is no longer populated for this tab. This is a UX regression — users lose the at-a-glance entity count on the tab label that all other tabs (Data Models, Files, Spreadsheets, Agents) still show.

To fix this, ServiceEntityTable could expose its total count via a callback prop or a shared context so the parent can still display the badge.

Performance: useEffect dependencies may cause double-fetch on state change

📄 openmetadata-ui/src/main/resources/ui/src/components/Service/ServiceEntityTable/ServiceEntityTable.tsx:246-254
The useEffect at line 254 includes both showDeleted, pageSize, searchValue as direct dependencies AND fetchEntities (which itself depends on all of those via useCallback). When any of these values change, fetchEntities's identity also changes, so the effect triggers from two dependency changes simultaneously. While React may batch these in some cases, it's cleaner to depend only on fetchEntities (which already captures all the relevant state) plus currentPage and isCustomizationPage.

...and 7 more resolved from earlier reviews

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud
Copy link
Copy Markdown

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add persona customization for service details page

3 participants