diff --git a/workspaces/bulk-import/.changeset/smart-crews-wish.md b/workspaces/bulk-import/.changeset/smart-crews-wish.md
new file mode 100644
index 00000000000..127298d0ca9
--- /dev/null
+++ b/workspaces/bulk-import/.changeset/smart-crews-wish.md
@@ -0,0 +1,5 @@
+---
+'@red-hat-developer-hub/backstage-plugin-bulk-import': patch
+---
+
+optimize bulk-import
diff --git a/workspaces/bulk-import/packages/app/src/modules/nav/Sidebar.tsx b/workspaces/bulk-import/packages/app/src/modules/nav/Sidebar.tsx
index 4d0943964a9..7d7058e33b9 100644
--- a/workspaces/bulk-import/packages/app/src/modules/nav/Sidebar.tsx
+++ b/workspaces/bulk-import/packages/app/src/modules/nav/Sidebar.tsx
@@ -20,38 +20,31 @@ import {
SidebarGroup,
SidebarItem,
SidebarScrollWrapper,
- SidebarSpace,
} from '@backstage/core-components';
import { NavContentBlueprint } from '@backstage/plugin-app-react';
-import {
- Settings as SidebarSettings,
- UserSettingsSignInAvatar,
-} from '@backstage/plugin-user-settings';
import MenuIcon from '@mui/icons-material/Menu';
import { SidebarLogo } from './SidebarLogo';
export const SidebarContent = NavContentBlueprint.make({
params: {
- component: ({ items }) => (
-
-
-
- }>
- {items.map((item, index) => (
-
- ))}
-
-
-
-
- }
- to="/settings"
- >
-
-
-
- ),
+ component: ({ navItems }) => {
+ return (
+
+
+
+ }>
+ {navItems.rest().map(item => (
+ item.icon}
+ />
+ ))}
+
+
+
+ );
+ },
},
});
diff --git a/workspaces/bulk-import/plugins/bulk-import/report-alpha.api.md b/workspaces/bulk-import/plugins/bulk-import/report-alpha.api.md
index 6c6dbcd5fc9..4580edaa67e 100644
--- a/workspaces/bulk-import/plugins/bulk-import/report-alpha.api.md
+++ b/workspaces/bulk-import/plugins/bulk-import/report-alpha.api.md
@@ -11,7 +11,6 @@ import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { FrontendModule } from '@backstage/frontend-plugin-api';
-import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconElement } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
@@ -116,13 +115,13 @@ export const bulkImportTranslationRef: TranslationRef<
readonly 'steps.generateCatalogInfoItems': string;
readonly 'steps.editPullRequest': string;
readonly 'steps.trackStatus': string;
- readonly 'addRepositories.generateCatalogInfo': string;
- readonly 'addRepositories.editPullRequest': string;
readonly 'addRepositories.approvalTool.title': string;
readonly 'addRepositories.approvalTool.tooltip': string;
readonly 'addRepositories.approvalTool.github': string;
readonly 'addRepositories.approvalTool.gitlab': string;
readonly 'addRepositories.approvalTool.description': string;
+ readonly 'addRepositories.generateCatalogInfo': string;
+ readonly 'addRepositories.editPullRequest': string;
readonly 'addRepositories.repositoryType.title': string;
readonly 'addRepositories.repositoryType.group': string;
readonly 'addRepositories.repositoryType.repository': string;
@@ -254,27 +253,6 @@ const _default: OverridableFrontendPlugin<
params: ApiFactory,
) => ExtensionBlueprintParams;
}>;
- 'nav-item:bulk-import': OverridableExtensionDefinition<{
- kind: 'nav-item';
- name: undefined;
- config: {};
- configInput: {};
- output: ExtensionDataRef<
- {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- },
- 'core.nav-item.target',
- {}
- >;
- inputs: {};
- params: {
- title: string;
- icon: IconComponent;
- routeRef: RouteRef;
- };
- }>;
'page:bulk-import': OverridableExtensionDefinition<{
kind: 'page';
name: undefined;
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/alpha.tsx b/workspaces/bulk-import/plugins/bulk-import/src/alpha.tsx
index 4640b4702e0..61822c3d83b 100644
--- a/workspaces/bulk-import/plugins/bulk-import/src/alpha.tsx
+++ b/workspaces/bulk-import/plugins/bulk-import/src/alpha.tsx
@@ -23,7 +23,6 @@ import {
createRouteRef,
createSubRouteRef,
identityApiRef,
- NavItemBlueprint,
PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { TranslationBlueprint } from '@backstage/plugin-app-react';
@@ -32,7 +31,7 @@ import {
bulkImportApiRef,
BulkImportBackendClient,
} from './api/BulkImportBackendClient';
-import BulkImportIcon from './components/BulkImportSidebarItem';
+import BulkImportIcon from './components/BulkImportIcon';
import { bulkImportTranslations } from './translations';
// NFS Route References - created using @backstage/frontend-plugin-api
@@ -73,41 +72,13 @@ const bulkImportPage = PageBlueprint.make({
params: {
path: '/bulk-import',
routeRef: rootRouteRef,
+ title: 'Bulk import',
+ icon: ,
noHeader: true,
loader: () => import('./components').then(({ Router }) => ),
},
});
-/**
- * Nav Item Extension
- *
- * NOTE: This nav item is always visible in the sidebar. Unlike the legacy
- * BulkImportSidebarItem component, NavItemBlueprint does not support runtime
- * permission checking because:
- *
- * 1. Extension factories run at app startup, before user authentication
- * 2. NavItemBlueprint only accepts static data (title, icon, routeRef)
- * 3. React hooks like usePermission cannot be used in extension factories
- *
- * Permission checking is handled at the PAGE level instead. Users without
- * the required permissions will see a permission error when accessing the page.
- *
- * For apps requiring permission-gated nav visibility, use the legacy
- * BulkImportSidebarItem component from the main package export:
- *
- * @example
- * ```tsx
- * import { BulkImportSidebarItem } from '@red-hat-developer-hub/backstage-plugin-bulk-import';
- * ```
- */
-const bulkImportNavItem = NavItemBlueprint.make({
- params: {
- title: 'Bulk import',
- routeRef: rootRouteRef,
- icon: BulkImportIcon,
- },
-});
-
/**
* The Bulk Import frontend plugin for the new Backstage frontend system.
*
@@ -115,7 +86,7 @@ const bulkImportNavItem = NavItemBlueprint.make({
*/
export default createFrontendPlugin({
pluginId: 'bulk-import',
- extensions: [bulkImportApi, bulkImportPage, bulkImportNavItem],
+ extensions: [bulkImportApi, bulkImportPage],
routes: {
root: rootRouteRef,
tasks: importHistoryRouteRef,
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/api/BulkImportBackendClient.ts b/workspaces/bulk-import/plugins/bulk-import/src/api/BulkImportBackendClient.ts
index 2fe6ab707be..8f847d50377 100644
--- a/workspaces/bulk-import/plugins/bulk-import/src/api/BulkImportBackendClient.ts
+++ b/workspaces/bulk-import/plugins/bulk-import/src/api/BulkImportBackendClient.ts
@@ -32,7 +32,7 @@ import {
OrgAndRepoResponse,
SortingOrderEnum,
} from '../types';
-import { getApi } from '../utils/repository-utils';
+import { getApi } from '../utils/getApi';
import { IBulkImportRESTPathProvider } from './BulkImportBackendClientBase';
import { OrchestratorBulkImportBackendClientPathProvider } from './OrchestratorBulkImportBackendClientPathProvider';
import { PRBulkImportBackendClientPathProvider } from './PRBulkImportBackendClientPathProvider';
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportIcon.tsx b/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportIcon.tsx
new file mode 100644
index 00000000000..b7e7d594c9a
--- /dev/null
+++ b/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportIcon.tsx
@@ -0,0 +1,39 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed 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 { useTheme } from '@mui/material/styles';
+
+import bulkImportBlackImg from '../images/BulkImportIcon-Black.svg';
+import bulkImportWhiteImg from '../images/BulkImportIcon-White.svg';
+
+/**
+ * @public
+ * Bulk Import Icon (kept free of sidebar/permission deps for NFS sync).
+ */
+export const BulkImportIcon = () => {
+ const theme = useTheme();
+ const isDarkTheme = theme.palette.mode === 'dark';
+
+ return (
+
+ );
+};
+
+export default BulkImportIcon;
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportSidebarItem.tsx b/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportSidebarItem.tsx
index 68b0ace81fb..f357c787395 100644
--- a/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportSidebarItem.tsx
+++ b/workspaces/bulk-import/plugins/bulk-import/src/components/BulkImportSidebarItem.tsx
@@ -18,32 +18,12 @@ import { SidebarItem } from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { usePermission } from '@backstage/plugin-permission-react';
-import { useTheme } from '@mui/material/styles';
-
import { bulkImportPermission } from '@red-hat-developer-hub/backstage-plugin-bulk-import-common';
import { useTranslation } from '../hooks/useTranslation';
-import { getImageForIconClass } from '../utils/icons';
-
-/**
- * @public
- * Bulk Import Icon
- */
-export const BulkImportIcon = () => {
- const theme = useTheme();
- const isDarkTheme = theme.palette.mode === 'dark';
- const iconClass = isDarkTheme
- ? 'icon-bulk-import-white'
- : 'icon-bulk-import-black';
+import { BulkImportIcon } from './BulkImportIcon';
- return (
-
- );
-};
+export { BulkImportIcon } from './BulkImportIcon';
export const BulkImportSidebarItem = () => {
const { t } = useTranslation();
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/index.ts b/workspaces/bulk-import/plugins/bulk-import/src/index.ts
index ee7761593d0..93d27b78172 100644
--- a/workspaces/bulk-import/plugins/bulk-import/src/index.ts
+++ b/workspaces/bulk-import/plugins/bulk-import/src/index.ts
@@ -28,4 +28,4 @@ export {
BulkImportSidebarItem,
} from './plugin';
-export { default as BulkImportIcon } from './components/BulkImportSidebarItem';
+export { default as BulkImportIcon } from './components/BulkImportIcon';
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/utils/getApi.ts b/workspaces/bulk-import/plugins/bulk-import/src/utils/getApi.ts
new file mode 100644
index 00000000000..73b75af94ca
--- /dev/null
+++ b/workspaces/bulk-import/plugins/bulk-import/src/utils/getApi.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright Red Hat, Inc.
+ *
+ * Licensed 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 { APITypes } from '../types';
+
+/** Builds bulk-import backend list URLs (kept out of repository-utils UI graph). */
+export const getApi = (
+ backendUrl: string,
+ page: number,
+ size: number,
+ searchString: string,
+ approvalTool: string,
+ options?: APITypes,
+) => {
+ const params = new URLSearchParams({
+ pagePerIntegration: String(page),
+ sizePerIntegration: String(size),
+ search: searchString,
+ approvalTool,
+ });
+
+ if (options?.fetchOrganizations) {
+ return `${backendUrl}/api/bulk-import/organizations?${params.toString()}`;
+ }
+ if (options?.orgName) {
+ const orgName = encodeURIComponent(options?.orgName);
+ return `${backendUrl}/api/bulk-import/organizations/${orgName}/repositories?${params.toString()}`;
+ }
+ return `${backendUrl}/api/bulk-import/repositories?${params.toString()}`;
+};
diff --git a/workspaces/bulk-import/plugins/bulk-import/src/utils/repository-utils.tsx b/workspaces/bulk-import/plugins/bulk-import/src/utils/repository-utils.tsx
index 80a2080df4b..a8b49bc7c47 100644
--- a/workspaces/bulk-import/plugins/bulk-import/src/utils/repository-utils.tsx
+++ b/workspaces/bulk-import/plugins/bulk-import/src/utils/repository-utils.tsx
@@ -19,16 +19,15 @@ import { Link, StatusOK } from '@backstage/core-components';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import Typography from '@mui/material/Typography';
-import * as jsyaml from 'js-yaml';
+import { loadAll } from 'js-yaml';
import { get } from 'lodash';
-import * as yaml from 'yaml';
+import { stringify } from 'yaml';
import * as yup from 'yup';
import { WaitingForPR } from '../components/WaitingForPR';
import {
AddedRepositories,
AddRepositoryData,
- APITypes,
ApprovalTool,
CreateImportJobRepository,
ErrorType,
@@ -50,6 +49,8 @@ import {
import { getWorkflowStatusInfo } from './orchestratorStatus';
import { getTaskStatusInfo } from './task-status';
+export { getApi } from './getApi';
+
export const TaskLink = ({
taskId,
t,
@@ -521,7 +522,7 @@ export const prepareDataForSubmission = (
organization: repo.orgName || '',
defaultBranch: repo.defaultBranch || '',
},
- catalogInfoContent: yaml.stringify(
+ catalogInfoContent: stringify(
repo.catalogInfoYaml?.prTemplate?.yaml,
null,
2,
@@ -540,31 +541,6 @@ export const prepareDataForSubmission = (
[],
);
-export const getApi = (
- backendUrl: string,
- page: number,
- size: number,
- searchString: string,
- approvalTool: string,
- options?: APITypes,
-) => {
- const params = new URLSearchParams({
- pagePerIntegration: String(page),
- sizePerIntegration: String(size),
- search: searchString,
- approvalTool,
- });
-
- if (options?.fetchOrganizations) {
- return `${backendUrl}/api/bulk-import/organizations?${params.toString()}`;
- }
- if (options?.orgName) {
- const orgName = encodeURIComponent(options?.orgName);
- return `${backendUrl}/api/bulk-import/organizations/${orgName}/repositories?${params.toString()}`;
- }
- return `${backendUrl}/api/bulk-import/repositories?${params.toString()}`;
-};
-
export const getCustomisedErrorMessage = (
status: (RepositoryStatus | string)[] | undefined,
t: (key: string) => string,
@@ -636,7 +612,7 @@ export const evaluatePRTemplate = (
): { pullReqPreview: PullRequestPreview; isInvalidEntity: boolean } => {
const gitProvider = isGithubJob(repositoryStatus) ? 'github' : 'gitlab';
try {
- const entity = jsyaml.loadAll(
+ const entity = loadAll(
repositoryStatus[gitProvider]?.pullRequest.catalogInfoContent ?? '',
)[0] as Entity;
const isInvalid =