From 5c01cea02facd9420ff42b43fabf7514b2761290 Mon Sep 17 00:00:00 2001 From: Anwesha Palit Date: Fri, 29 May 2026 09:35:22 +0530 Subject: [PATCH] feat: removal of tektonhub --- console-extensions.json | 31 +-- .../create-from-builder-page.feature | 52 ----- .../en/plugin__pipelines-console-plugin.json | 5 +- .../es/plugin__pipelines-console-plugin.json | 2 - .../fr/plugin__pipelines-console-plugin.json | 2 - .../ja/plugin__pipelines-console-plugin.json | 2 - .../ko/plugin__pipelines-console-plugin.json | 2 - .../zh/plugin__pipelines-console-plugin.json | 2 - .../catalog/apis/__tests__/tektonHub.spec.ts | 202 ------------------ src/components/catalog/apis/artifactHub.ts | 9 + src/components/catalog/apis/tektonHub.ts | 95 -------- src/components/catalog/catalog-utils.ts | 28 ++- src/components/catalog/providers/index.ts | 2 - .../providers/useArtifactHubTasksProvider.tsx | 19 +- .../providers/useTekonHubTasksProvider.tsx | 106 --------- .../pipeline-builder/PipelineBuilderForm.tsx | 7 +- .../quick-search/QuickSearchModalBody.tsx | 8 +- .../quick-search/utils/quick-search-utils.tsx | 2 +- .../task-quicksearch/PipelineQuickSearch.tsx | 73 +++---- .../PipelineQuickSearchDetails.tsx | 84 ++------ .../PipelineQuickSearchVersionDropdown.tsx | 8 +- .../PipelineQuicksearchDetails.spec.tsx | 179 +++------------- .../__tests__/catalog-item-data.ts | 173 +++------------ .../pipeline-quicksearch-utils.spec.ts | 97 +++++---- src/components/task-quicksearch/const.ts | 1 - .../pipeline-quicksearch-utils.ts | 92 +------- src/consts.ts | 1 + src/models.ts | 17 -- src/test-data/tektonhub-data.ts | 88 -------- src/types/hub.ts | 29 --- 30 files changed, 225 insertions(+), 1193 deletions(-) delete mode 100644 src/components/catalog/apis/__tests__/tektonHub.spec.ts delete mode 100644 src/components/catalog/apis/tektonHub.ts delete mode 100644 src/components/catalog/providers/useTekonHubTasksProvider.tsx delete mode 100644 src/test-data/tektonhub-data.ts delete mode 100644 src/types/hub.ts diff --git a/console-extensions.json b/console-extensions.json index b61dbffc..43cc004c 100644 --- a/console-extensions.json +++ b/console-extensions.json @@ -324,20 +324,6 @@ "abbr": "TC" } }, - { - "type": "console.model-metadata", - "properties": { - "model": { - "group": "operator.tekton.dev", - "version": "v1alpha1", - "kind": "TektonHub" - }, - "color": "#38812f", - "label": "%plugin__pipelines-console-plugin~TektonHub%", - "labelPlural": "%plugin__pipelines-console-plugin~TektonHubs%", - "abbr": "TH" - } - }, { "type": "console.context-provider", "properties": { @@ -1480,7 +1466,7 @@ "properties": { "type": "Community", "title": "%plugin__pipelines-console-plugin~Community%", - "catalogDescription": "%plugin__pipelines-console-plugin~Browse tekton hub tasks.%" + "catalogDescription": "%plugin__pipelines-console-plugin~Browse community tasks.%" }, "flags": { "required": [ @@ -1504,21 +1490,6 @@ ] } }, - { - "type": "console.catalog/item-provider", - "properties": { - "catalogId": "pipelines-task-catalog", - "type": "Community", - "title": "%plugin__pipelines-console-plugin~Tasks%", - "provider": { "$codeRef": "catalog.TektonHubTaskProvider" } - }, - "flags": { - "required": [ - "OPENSHIFT_PIPELINE", - "HIDE_STATIC_PIPELINE_PLUGIN_PIPELINE_BUILDER" - ] - } - }, { "type": "console.catalog/item-provider", "properties": { diff --git a/integration-tests/cypress/features/pipelines/create-from-builder-page.feature b/integration-tests/cypress/features/pipelines/create-from-builder-page.feature index a53fc756..abcb6bd0 100644 --- a/integration-tests/cypress/features/pipelines/create-from-builder-page.feature +++ b/integration-tests/cypress/features/pipelines/create-from-builder-page.feature @@ -118,33 +118,6 @@ Feature: Create the pipeline from builder page Then user can see the task in series gets removed - @regression @broken-test - Scenario Outline: Create a pipeline with TektonHub task not present in cluster from pipeline builder page: P-02-TC09 - Given user is at Pipeline Builder page - When user enters pipeline name as "" - And user clicks Add task button under Tasks section - And user searches and select "" in the list of items based on the "TektonHub" provider in quick search bar - And user clicks on Install and add button - And user clicks Create button on Pipeline Builder page - Then user will be redirected to Pipeline Details page with header name "" - - Examples: - | pipeline_name | task_name | - | ptask-1 | kn | - - - @regression @broken-test - Scenario: Upgrade tasks that are already installed on the cluster in pipeline builder page: P-02-TC10 - Given user is at Pipeline Builder page - When user enters pipeline name as "pipeline-client" - And user installs and removes "openshift-client" of "TektonHub" provider - And user clicks Add task button under Tasks section - And user searches "openshift-client" in quick search bar - And user changes version to "0.1" - And user clicks on Update and Add button - And user clicks Create button on Pipeline Builder page - Then user will be redirected to Pipeline Details page with header name "pipeline-client" - @regression @manual Scenario: Create the pipeline from yaml editor: P-02-TC11 @@ -315,31 +288,6 @@ Feature: Create the pipeline from builder page Then user will be able to see the output in sum and multiply task - @regression @manual - Scenario: Disable Tektonhub integration in the pipeline builder : P-02-TC22 - Given user is at Search page - And user searches 'TektonConfig' in Resources dropdown - And user selects config with apiVersion operator.openshift.io/v1 option from Resources dropdown - And user clicks on "config" Name in TektonConfigs - And user switches to YAML tab - And user adds value of "spec.hub.params.value" as "false" - And user clicks on Save button - And user clicks on Pipeline tab in navigation menu - And user clicks Create Pipeline button - And user clicks Add task button under Tasks section - And user types 'git' - Then user will see Task only - - - @regression @manual - Scenario: Pipeline builder to support local Tekton Hub instances : P-02-TC23 - Given user has setup tektonhub instance - # Refer to document https://docs.google.com/document/d/1HImc2DdtFKMWgk5dTm8Ib-I3wgnVXxdHxzX8Cn0jrZA/edit?usp=sharing for setting up tektonhub insance - And user is at Pipeline Builder page - When user clicks on Add task button under Tasks section - And user searches a tasks that is available in the local tektonhub instance - Then user will see the intended community task - # Marked following test broken due to issue https://issues.redhat.com/browse/OCPBUGS-59536 @regression @broken-test Scenario Outline: Start pipeline with parameter of type array: P-02-TC24 diff --git a/locales/en/plugin__pipelines-console-plugin.json b/locales/en/plugin__pipelines-console-plugin.json index cfb90e11..8b1077cb 100644 --- a/locales/en/plugin__pipelines-console-plugin.json +++ b/locales/en/plugin__pipelines-console-plugin.json @@ -84,8 +84,8 @@ "Block": "Block", "Branch": "Branch", "Branch/Tag": "Branch/Tag", + "Browse community tasks.": "Browse community tasks.", "Browse for openshift pipeline tasks available in the cluster.": "Browse for openshift pipeline tasks available in the cluster.", - "Browse tekton hub tasks.": "Browse tekton hub tasks.", "Browse...": "Browse...", "Cancel": "Cancel", "Cancelled": "Cancelled", @@ -417,7 +417,6 @@ "Quick search": "Quick search", "Quick search bar": "Quick search bar", "Quick search list": "Quick search list", - "Read more": "Read more", "Read more about setting up webhook": "Read more about setting up webhook", "Read only (ROX)": "Read only (ROX)", "Reason": "Reason", @@ -554,8 +553,6 @@ "Tekton results": "Tekton results", "TektonConfig": "TektonConfig", "TektonConfigs": "TektonConfigs", - "TektonHub": "TektonHub", - "TektonHubs": "TektonHubs", "TektonResult": "TektonResult", "TektonResults": "TektonResults", "The base server url (e.g. https://github.com)": "The base server url (e.g. https://github.com)", diff --git a/locales/es/plugin__pipelines-console-plugin.json b/locales/es/plugin__pipelines-console-plugin.json index 88c24794..b2c317b6 100644 --- a/locales/es/plugin__pipelines-console-plugin.json +++ b/locales/es/plugin__pipelines-console-plugin.json @@ -554,8 +554,6 @@ "Tekton results": "Resultados de Tekton", "TektonConfig": "TektonConfig", "TektonConfigs": "TektonConfigs", - "TektonHub": "TektonHub", - "TektonHubs": "TektonHubs", "TektonResult": "TektonResult", "TektonResults": "TektonResults", "The base server url (e.g. https://github.com)": "La URL del servidor base (por ejemplo, https://github.com)", diff --git a/locales/fr/plugin__pipelines-console-plugin.json b/locales/fr/plugin__pipelines-console-plugin.json index a9c945d7..f54ec281 100644 --- a/locales/fr/plugin__pipelines-console-plugin.json +++ b/locales/fr/plugin__pipelines-console-plugin.json @@ -554,8 +554,6 @@ "Tekton results": "Résultats Tekton", "TektonConfig": "Configuration de Tekton", "TektonConfigs": "Configurations de Tekton", - "TektonHub": "Hub Tekton", - "TektonHubs": "Hubs Tekton", "TektonResult": "Résultat Tekton", "TektonResults": "Résultats Tekton", "The base server url (e.g. https://github.com)": "URL du serveur de base (par exemple https://github.com)", diff --git a/locales/ja/plugin__pipelines-console-plugin.json b/locales/ja/plugin__pipelines-console-plugin.json index af956c29..a929330e 100644 --- a/locales/ja/plugin__pipelines-console-plugin.json +++ b/locales/ja/plugin__pipelines-console-plugin.json @@ -554,8 +554,6 @@ "Tekton results": "テクトンの結果", "TektonConfig": "TektonConfig", "TektonConfigs": "TektonConfigs", - "TektonHub": "TektonHub", - "TektonHubs": "TektonHubs", "TektonResult": "TektonResult", "TektonResults": "TektonResults", "The base server url (e.g. https://github.com)": "ベースサーバーの URL (例: https://github.com)", diff --git a/locales/ko/plugin__pipelines-console-plugin.json b/locales/ko/plugin__pipelines-console-plugin.json index a3aa868d..c88dd5c2 100644 --- a/locales/ko/plugin__pipelines-console-plugin.json +++ b/locales/ko/plugin__pipelines-console-plugin.json @@ -554,8 +554,6 @@ "Tekton results": "Tekton 결과", "TektonConfig": "TektonConfig", "TektonConfigs": "tektonconfigs", - "TektonHub": "TektonHub", - "TektonHubs": "TektonHubs", "TektonResult": "TektonResult", "TektonResults": "TektonResults", "The base server url (e.g. https://github.com)": "기본 서버 URL (예: https://github.com)", diff --git a/locales/zh/plugin__pipelines-console-plugin.json b/locales/zh/plugin__pipelines-console-plugin.json index 93a39178..f7361931 100644 --- a/locales/zh/plugin__pipelines-console-plugin.json +++ b/locales/zh/plugin__pipelines-console-plugin.json @@ -554,8 +554,6 @@ "Tekton results": "Tekton 结果", "TektonConfig": "TektonConfig", "TektonConfigs": "TektonConfigs", - "TektonHub": "TektonHub", - "TektonHubs": "TektonHubs", "TektonResult": "TektonResult", "TektonResults": "TektonResults", "The base server url (e.g. https://github.com)": "基本服务器 url(如 https://github.com)", diff --git a/src/components/catalog/apis/__tests__/tektonHub.spec.ts b/src/components/catalog/apis/__tests__/tektonHub.spec.ts deleted file mode 100644 index 51b08032..00000000 --- a/src/components/catalog/apis/__tests__/tektonHub.spec.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { act } from 'react-dom/test-utils'; -import { sampleTektonHubCR } from '../../../../test-data/tektonhub-data'; -import { testHook } from '../../../../test-data/utils/hooks-utils'; -import { useK8sGet } from '../../../hooks/use-k8sGet-hook'; -import { sampleTektonHubCatalogItem } from '../../../task-quicksearch/__tests__/catalog-item-data'; -import { - getApiResponse, - getHubUIPath, - TEKTON_HUB_API_ENDPOINT, - TEKTON_HUB_ENDPOINT, - TektonHubTaskVersion, - useInclusterTektonHubURLs, -} from '../tektonHub'; - -jest.mock('../../../hooks/use-k8sGet-hook', () => ({ - useK8sGet: jest.fn(), -})); - -const emptyHeaders = new Headers(); -const apiResults = ({ - ok, - status, - versions, -}: { - ok: boolean; - status?: number; - versions?: TektonHubTaskVersion[]; -}) => - Promise.resolve({ - headers: emptyHeaders, - ok, - ...(status && { status }), - json: () => ({ - data: { - versions, - }, - }), - }); - -// setUtilsConfig({ appFetch: appInternalFetch }); - -xdescribe('getApiResponse', () => { - beforeEach(() => { - (window as any).fetch = jest.fn(() => - apiResults({ - ok: true, - status: 200, - versions: sampleTektonHubCatalogItem.attributes.versions, - }), - ); - window.console.warn = jest.fn(() => ''); - }); - - afterEach(() => { - (window.fetch as jest.Mock).mockClear(); - (window.console.warn as jest.Mock).mockClear(); - }); - - it('should throw error if the response status is not 200', async () => { - (window as any).fetch = jest.fn(() => - Promise.resolve({ status: 404, headers: emptyHeaders }), - ); - try { - await getApiResponse('testurl'); - fail('Expect an error if the response status is not 200'); - } catch (e) { - expect(e?.code).toBe(404); - } - }); - - it('should throw error if the response ok is false', async () => { - (window as any).fetch = jest.fn(() => - Promise.resolve({ ok: false, status: 500, headers: emptyHeaders }), - ); - - try { - await getApiResponse('testurl'); - fail('Expect an error if the response ok is false'); - } catch (e) { - expect(e?.response?.ok).toBe(false); - } - }); - - it('should return a valid response', async () => { - const response = await getApiResponse('testurl'); - expect(response.data).toBeDefined(); - expect(response.data.versions).toBeDefined(); - }); -}); - -xdescribe('getHubUIPath', () => { - it('should return null if the path is not set', () => { - expect(getHubUIPath('')).toBeNull(); - expect(getHubUIPath(null)).toBeNull(); - expect(getHubUIPath(undefined)).toBeNull(); - }); - - it('should return a value if the path is set', () => { - expect(getHubUIPath('test')).not.toBeNull(); - expect(getHubUIPath('test-path')).toBe(`${TEKTON_HUB_ENDPOINT}/test-path`); - }); - - it('should return custom path url if the baseurl param is passed as a second argument', () => { - expect(getHubUIPath('test-path', 'https://hub-ui.com')).toBe( - `https://hub-ui.com/test-path`, - ); - }); -}); - -xdescribe('useInClusterTektonHubURLs:', () => { - it('should return public tekton hub endpoint incase of hub CR not found', async () => { - (useK8sGet as jest.Mock).mockReturnValue([ - null, - true, - 'error fetching tektonhubs.operator.tekton.dev "hub" not found', - ]); - const { result } = testHook(() => useInclusterTektonHubURLs()); - expect(result.current).toEqual({ - loaded: true, - apiURL: TEKTON_HUB_API_ENDPOINT, - uiURL: TEKTON_HUB_ENDPOINT, - }); - }); - - it('should return public tekton hub endpoint when hub is installed and if status field missing', async () => { - const sampleHubWithoutApiURL = { - ...sampleTektonHubCR, - status: null, - }; - (useK8sGet as jest.Mock).mockReturnValue([ - sampleHubWithoutApiURL, - true, - '', - ]); - const { result } = testHook(() => useInclusterTektonHubURLs()); - expect(result.current).toEqual({ - loaded: true, - apiURL: TEKTON_HUB_API_ENDPOINT, - uiURL: TEKTON_HUB_ENDPOINT, - }); - }); - - it('should return public tekton hub endpoint when hub is installed but urls are missing', async () => { - const sampleHubWithoutApiURL = { - ...sampleTektonHubCR, - status: { - ...sampleTektonHubCR.status, - apiUrl: '', - uiUrl: '', - }, - }; - (useK8sGet as jest.Mock).mockReturnValue([ - sampleHubWithoutApiURL, - true, - '', - ]); - const { result } = testHook(() => useInclusterTektonHubURLs()); - expect(result.current).toEqual({ - loaded: true, - apiURL: TEKTON_HUB_API_ENDPOINT, - uiURL: TEKTON_HUB_ENDPOINT, - }); - }); - - it('should return incluster tekton hub endpoint when hub is installed', async () => { - (useK8sGet as jest.Mock).mockReturnValue([sampleTektonHubCR, true, '']); - const { result } = testHook(() => useInclusterTektonHubURLs()); - expect(result.current).toEqual({ - loaded: true, - apiURL: sampleTektonHubCR.status.apiUrl, - uiURL: sampleTektonHubCR.status.uiUrl, - }); - }); - - it('should return the apiUrl and uiUrl when the api call the hub has urls in the status', async () => { - const sampleHubWithoutApiURL = { - ...sampleTektonHubCR, - status: null, - }; - (useK8sGet as jest.Mock).mockReturnValue([ - sampleHubWithoutApiURL, - true, - '', - ]); - const { result, rerender } = testHook(() => useInclusterTektonHubURLs()); - expect(result.current).toEqual({ - loaded: true, - apiURL: TEKTON_HUB_API_ENDPOINT, - uiURL: TEKTON_HUB_ENDPOINT, - }); - - (useK8sGet as jest.Mock).mockReturnValue([sampleTektonHubCR, true, '']); - act(() => { - rerender(); - }); - expect(result.current).toEqual({ - loaded: true, - apiURL: sampleTektonHubCR.status.apiUrl, - uiURL: sampleTektonHubCR.status.uiUrl, - }); - }); -}); diff --git a/src/components/catalog/apis/artifactHub.ts b/src/components/catalog/apis/artifactHub.ts index c78f3085..da6fbf77 100644 --- a/src/components/catalog/apis/artifactHub.ts +++ b/src/components/catalog/apis/artifactHub.ts @@ -58,6 +58,7 @@ export const useGetArtifactHubTasks = ( const fetchTasks = async () => { try { if (!hasPermission) { + setResult([]); setLoaded(true); return; } @@ -104,6 +105,11 @@ export const createArtifactHubTask = ( isDevConsoleProxyAvailable?: boolean, customName?: string, ) => { + if (!url || typeof url !== 'string') { + return Promise.reject( + new Error('ArtifactHub task content URL is missing or invalid'), + ); + } const fetchTask = async (): Promise => { if (isDevConsoleProxyAvailable) { const yamlPath = url.startsWith(GITHUB_BASE_URL) @@ -152,6 +158,9 @@ export const updateArtifactHubTask = async ( version: string, isDevConsoleProxyAvailable?: boolean, ) => { + if (!url || typeof url !== 'string') { + throw new Error('ArtifactHub task content URL is missing or invalid'); + } const fetchTask = async (): Promise => { if (isDevConsoleProxyAvailable) { const yamlPath = url.startsWith(GITHUB_BASE_URL) diff --git a/src/components/catalog/apis/tektonHub.ts b/src/components/catalog/apis/tektonHub.ts deleted file mode 100644 index ec38c13b..00000000 --- a/src/components/catalog/apis/tektonHub.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { consoleFetch } from '@openshift-console/dynamic-plugin-sdk'; -import { TektonHubModel } from '../../../models'; -import { TektonHub } from '../../../types/hub'; -import { useK8sGet } from '../../hooks/use-k8sGet-hook'; -import useApiResponse, { ApiResult } from '../hooks/useApiResponse'; - -export type TektonHubItem = { - id: number; - name: string; -}; -export type TektonHubCategory = TektonHubItem; - -export type TektonHubTag = TektonHubItem; - -export type TektonHubPlatform = TektonHubItem; - -export type TektonHubCatalog = TektonHubItem & { - type: string; -}; - -export type TektonHubTaskVersion = { - id: number; - version: string; - hubURLPath: string; - rawURL: string; - webURL: string; - platforms: TektonHubPlatform[]; -}; - -export type TektonHubLatestVersion = TektonHubTaskVersion & { - displayName: string; - description: string; - minPipelinesVersion: string; - updatedAt: string; -}; - -export type TektonHubTask = { - id: number; - name: string; - categories: TektonHubCategory[]; - catalog: TektonHubCatalog; - platforms: TektonHubPlatform[]; - kind: string; - latestVersion: TektonHubLatestVersion; - tags: TektonHubTag[]; - rating: number; -}; -export const TEKTON_HUB_API_VERSION = 'v1'; -export const TEKTON_HUB_API_ENDPOINT = 'https://api.hub.tekton.dev'; -export const TEKTON_HUB_ENDPOINT = `https://hub.tekton.dev`; -export const TEKTON_HUB_INTEGRATION_KEY = 'enable-devconsole-integration'; - -export const getHubUIPath = ( - path = '', - baseURL: string = TEKTON_HUB_ENDPOINT, -): string => { - if (!path) { - return null; - } - return `${baseURL}/${path}`; -}; - -export const getApiResponse = async (url: string) => - (await consoleFetch(url)).json(); - -export const useInclusterTektonHubURLs = () => { - const [hub, loaded] = useK8sGet(TektonHubModel, 'hub'); - // check in-cluster hub exists, if yes use incluster hub instance api url and ui url - return { - loaded, - apiURL: hub?.status?.apiUrl || TEKTON_HUB_API_ENDPOINT, - uiURL: hub?.status?.uiUrl || TEKTON_HUB_ENDPOINT, - }; -}; - -export const useTektonHubResources = ( - baseURL, - hasPermission: boolean, -): ApiResult => { - return useApiResponse( - `${baseURL}/${TEKTON_HUB_API_VERSION}/resources`, - hasPermission, - ); -}; - -export const getTektonHubTaskVersions = async ( - resourceId: string, - baseURL?: string, -): Promise => { - const API_BASE_URL = baseURL || TEKTON_HUB_API_ENDPOINT; - const response = await getApiResponse( - `${API_BASE_URL}/${TEKTON_HUB_API_VERSION}/resource/${resourceId}/versions`, - ); - return response?.data?.versions ?? []; -}; diff --git a/src/components/catalog/catalog-utils.ts b/src/components/catalog/catalog-utils.ts index d71e6d95..2a4c5abe 100644 --- a/src/components/catalog/catalog-utils.ts +++ b/src/components/catalog/catalog-utils.ts @@ -7,10 +7,10 @@ import { useResolvedExtensions, } from '@openshift-console/dynamic-plugin-sdk'; import * as _ from 'lodash'; +import { HUB_INTEGRATION_KEY } from '../../consts'; import { TektonConfigModel } from '../../models'; import { useK8sGet } from '../hooks/use-k8sGet-hook'; import * as catalogImg from '../imgs/catalog-icon.svg'; -import { TEKTON_HUB_INTEGRATION_KEY, TektonHubTask } from './apis/tektonHub'; enum CatalogVisibilityState { Enabled = 'Enabled', @@ -152,28 +152,24 @@ export const getIconProps = (item: CatalogItem) => { return { iconImg: catalogImg, iconClass: null }; }; -export const getClusterPlatform = (): string => - `${(window as any).SERVER_FLAGS.GOOS}/${(window as any).SERVER_FLAGS.GOARCH}`; - -export const filterBySupportedPlatforms = (task: TektonHubTask): boolean => { - const supportedPlatforms = task?.platforms.map((p) => p.name) ?? []; - return supportedPlatforms.includes(getClusterPlatform()); -}; - -export const useTektonHubIntegration = () => { +/* Returns [artifactHubIntegrationStatus, IsIntegrationLoaded] */ +export const useHubIntegration = (): [boolean, boolean] => { const [config, configLoaded, configLoadErr] = useK8sGet( TektonConfigModel, 'config', ); if (!configLoaded) { - return false; + return [false, false]; } - // return false only if TEKTON_HUB_INTEGRATION_KEY value is set to 'false' - if (config && configLoaded && !configLoadErr) { + if (config && !configLoadErr) { const devconsoleIntegrationEnabled = config.spec?.hub?.params?.find( - (p) => p.name === TEKTON_HUB_INTEGRATION_KEY, + (p) => p.name === HUB_INTEGRATION_KEY, ); - return devconsoleIntegrationEnabled?.value?.toLowerCase() !== 'false'; + return [ + devconsoleIntegrationEnabled?.value?.toLowerCase() !== 'false', + true, + ]; } - return true; + // If the param "enable-devconsole-integration" is unset then integration defaults to enabled. ArtifactHub tasks will show up -- same behavior as setting it to "true". + return [true, true]; }; diff --git a/src/components/catalog/providers/index.ts b/src/components/catalog/providers/index.ts index 50f80bc7..20dfe17c 100644 --- a/src/components/catalog/providers/index.ts +++ b/src/components/catalog/providers/index.ts @@ -1,5 +1,3 @@ export { default as TektonTaskProvider } from './useTasksProvider'; -export { default as TektonHubTaskProvider } from './useTekonHubTasksProvider'; - export { default as ArtifactHubTaskProvider } from './useArtifactHubTasksProvider'; diff --git a/src/components/catalog/providers/useArtifactHubTasksProvider.tsx b/src/components/catalog/providers/useArtifactHubTasksProvider.tsx index f4a00995..cf350119 100644 --- a/src/components/catalog/providers/useArtifactHubTasksProvider.tsx +++ b/src/components/catalog/providers/useArtifactHubTasksProvider.tsx @@ -6,7 +6,6 @@ import { updateArtifactHubTask, useGetArtifactHubTasks, } from '../apis/artifactHub'; -import { TektonHubTask } from '../apis/tektonHub'; import { CatalogItem, ExtensionHook, @@ -17,7 +16,7 @@ import { import { TaskProviders } from '../../task-quicksearch/pipeline-quicksearch-utils'; import { TaskModel } from '../../../models'; import { getReferenceForModel } from '../../pipelines-overview/utils'; -import { useTektonHubIntegration } from '../catalog-utils'; +import { useHubIntegration } from '../catalog-utils'; import { t } from '../../utils/common-utils'; import { ArtifactHubTask, FLAGS } from '../../../types'; import useTasksProvider from './useTasksProvider'; @@ -75,6 +74,10 @@ export const normalizeArtifactHubTasks = ( selectedVersion, isDevConsoleProxyAvailable, ).catch((error) => { + console.warn( + 'Error updating ArtifactHub task - callsite useArtifactHubTasksProvider:', + error, + ); setFailedTasks((prev) => [ ...prev, selectedItem.data.task.name, @@ -113,7 +116,8 @@ export const normalizeArtifactHubTasks = ( const useArtifactHubTasksProvider: ExtensionHook = ({ namespace, }): [CatalogItem[], boolean, string] => { - const artifactHubIntegration = useTektonHubIntegration(); + const [artifactHubIntegrationStatus, isIntegrationLoaded] = + useHubIntegration(); const [tektonTasks] = useTasksProvider({}); const isDevConsoleProxyAvailable = useFlag(FLAGS.DEVCONSOLE_PROXY); const canCreateTask = useAccessReview({ @@ -131,12 +135,13 @@ const useArtifactHubTasksProvider: ExtensionHook = ({ }); const [artifactHubTasks, tasksLoaded, tasksError] = useGetArtifactHubTasks( - canCreateTask && canUpdateTask && artifactHubIntegration, + isIntegrationLoaded && + canCreateTask && + canUpdateTask && + artifactHubIntegrationStatus, isDevConsoleProxyAvailable, ); - const normalizedArtifactHubTasks = React.useMemo< - CatalogItem[] - >( + const normalizedArtifactHubTasks = React.useMemo( () => normalizeArtifactHubTasks(artifactHubTasks, tektonTasks), [artifactHubTasks, tektonTasks], ); diff --git a/src/components/catalog/providers/useTekonHubTasksProvider.tsx b/src/components/catalog/providers/useTekonHubTasksProvider.tsx deleted file mode 100644 index a7e78f25..00000000 --- a/src/components/catalog/providers/useTekonHubTasksProvider.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react'; -import { Label } from '@patternfly/react-core'; -import { - TektonHubTask, - useInclusterTektonHubURLs, - useTektonHubResources, -} from '../apis/tektonHub'; -import { - filterBySupportedPlatforms, - useTektonHubIntegration, -} from '../catalog-utils'; -import { - CatalogItem, - ExtensionHook, - ResourceIcon, - useAccessReview, -} from '@openshift-console/dynamic-plugin-sdk'; -import { TaskModel } from '../../../models'; -import { TaskProviders } from '../../task-quicksearch/pipeline-quicksearch-utils'; -import { getReferenceForModel } from '../../pipelines-overview/utils'; -import { t } from '../../utils/common-utils'; - -const normalizeTektonHubTasks = ( - tektonHubTasks: TektonHubTask[], - apiURL: string, - uiURL: string, -): CatalogItem[] => { - const normalizedTektonHubTasks: CatalogItem[] = tektonHubTasks - .filter(filterBySupportedPlatforms) - .reduce((acc, task) => { - if (task.kind !== TaskModel.kind) { - return acc; - } - const { id, name } = task; - const { description } = task.latestVersion; - const provider = TaskProviders.tektonHub; - const tags = task.tags?.map((t) => t.name) ?? []; - const categories = task.categories?.map((ct) => ct.name) ?? []; - const [secondaryLabelName] = categories; - const versions = []; - const normalizedTektonTask: CatalogItem = { - uid: id.toString(), - type: TaskProviders.community, - name, - description, - provider, - tags, - secondaryLabel: secondaryLabelName && ( - - ), - icon: { - node: , - }, - attributes: { installed: '', versions, categories, apiURL, uiURL }, - cta: { - label: t('Add'), - }, - data: task, - }; - acc.push(normalizedTektonTask); - - return acc; - }, []); - - return normalizedTektonHubTasks; -}; - -const useTektonHubTasksProvider: ExtensionHook = ({ - namespace, -}): [CatalogItem[], boolean, string] => { - const [normalizedTektonHubTasks, setNormalizedTektonHubTasks] = - React.useState[]>([]); - - const canCreateTask = useAccessReview({ - group: TaskModel.apiGroup, - resource: TaskModel.plural, - namespace, - verb: 'create', - }); - - const canUpdateTask = useAccessReview({ - group: TaskModel.apiGroup, - resource: TaskModel.plural, - namespace, - verb: 'update', - }); - - const integrationEnabled = useTektonHubIntegration(); - const { apiURL, uiURL, loaded: baseURLLoaded } = useInclusterTektonHubURLs(); - - const [tektonHubTasks, tasksLoaded, tasksError] = useTektonHubResources( - apiURL, - canCreateTask && canUpdateTask && integrationEnabled && baseURLLoaded, - ); - - React.useMemo( - () => - setNormalizedTektonHubTasks( - normalizeTektonHubTasks(tektonHubTasks, apiURL, uiURL), - ), - [apiURL, tektonHubTasks, uiURL], - ); - return [normalizedTektonHubTasks, tasksLoaded, tasksError]; -}; - -export default useTektonHubTasksProvider; diff --git a/src/components/pipeline-builder/PipelineBuilderForm.tsx b/src/components/pipeline-builder/PipelineBuilderForm.tsx index c8df3a82..b5c87412 100644 --- a/src/components/pipeline-builder/PipelineBuilderForm.tsx +++ b/src/components/pipeline-builder/PipelineBuilderForm.tsx @@ -199,7 +199,12 @@ const PipelineBuilderForm: React.FC = (props) => { selectedTask ? ( setSelectedTask(null)} resourceList={formData.resources || []} workspaceList={formData.workspaces || []} diff --git a/src/components/quick-search/QuickSearchModalBody.tsx b/src/components/quick-search/QuickSearchModalBody.tsx index c3322add..a6d28224 100644 --- a/src/components/quick-search/QuickSearchModalBody.tsx +++ b/src/components/quick-search/QuickSearchModalBody.tsx @@ -19,6 +19,7 @@ import { fetchArtifactHubTasks } from '../catalog/apis/artifactHub'; import { normalizeArtifactHubTasks } from '../catalog/providers/useArtifactHubTasksProvider'; import { TaskSearchCallback } from '../pipeline-builder/types'; import useTasksProvider from '../catalog/providers/useTasksProvider'; +import { useHubIntegration } from '../catalog/catalog-utils'; import './QuickSearchModalBody.scss'; import { FLAGS } from '../../types'; @@ -58,6 +59,7 @@ const QuickSearchModalBody: React.FC = ({ const MIN_WIDTH = 225; const history = useHistory(); const isDevConsoleProxyAvailable = useFlag(FLAGS.DEVCONSOLE_PROXY); + const [artifactHubIntegrationStatus] = useHubIntegration(); const [catalogItems, setCatalogItems] = React.useState(null); const [catalogTypes, setCatalogTypes] = React.useState([]); const [isRndActive, setIsRndActive] = React.useState(false); @@ -164,7 +166,9 @@ const QuickSearchModalBody: React.FC = ({ } const [artifactHubResults, catalogResults] = await Promise.all([ - fetchArtifactHubTasks(value), + artifactHubIntegrationStatus + ? fetchArtifactHubTasks(value) // only fetch artifact hub tasks if the integration is enabled + : Promise.resolve([]), // if the integration is disabled, return an empty array searchCatalog(value), ]); @@ -196,7 +200,7 @@ const QuickSearchModalBody: React.FC = ({ setQueryArgument('catalogSearch', value); setSelectedItemId(null); }, - [searchCatalog], + [searchCatalog, artifactHubIntegrationStatus], ); const debouncedHandleSearch = React.useMemo( diff --git a/src/components/quick-search/utils/quick-search-utils.tsx b/src/components/quick-search/utils/quick-search-utils.tsx index c2fb5702..069ba021 100644 --- a/src/components/quick-search/utils/quick-search-utils.tsx +++ b/src/components/quick-search/utils/quick-search-utils.tsx @@ -19,9 +19,9 @@ export const handleCta = async ( if (callback) { closeModal(); await callback({ - ...callbackProps, selectedVersion: item.data?.version ?? item.data?.task?.version, selectedItem: item, + ...callbackProps, }); removeQueryArgument('catalogSearch'); } else history.push(href); diff --git a/src/components/task-quicksearch/PipelineQuickSearch.tsx b/src/components/task-quicksearch/PipelineQuickSearch.tsx index a8d56e99..86ef6ce8 100644 --- a/src/components/task-quicksearch/PipelineQuickSearch.tsx +++ b/src/components/task-quicksearch/PipelineQuickSearch.tsx @@ -12,13 +12,11 @@ import { UpdateTasksCallback, } from '../pipeline-builder/types'; import { - createTask, findInstalledTask, getSelectedVersionUrl, isArtifactHubTask, isTaskSearchable, TaskProviders, - updateTask, } from './pipeline-quicksearch-utils'; import { safeName } from '../pipeline-builder/utils'; import PipelineQuickSearchDetails from './PipelineQuickSearchDetails'; @@ -28,6 +26,7 @@ import { QuickSearchProviders } from './quick-search-types'; import { QuickSearchController } from '../quick-search'; import { createArtifactHubTask, + getArtifactHubTaskDetails, updateArtifactHubTask, } from '../catalog/apis/artifactHub'; import { FLAGS } from '../../types'; @@ -112,62 +111,44 @@ const Contents: React.FC< const catalogServiceItems = catalogService.items.reduce((acc, item) => { const installedTask = findInstalledTask(catalogService.items, item); - if ( - (item.provider === TaskProviders.artifactHub || - item.provider === TaskProviders.tektonHub) && + item.provider === TaskProviders.artifactHub && item.type !== TaskProviders.redhat ) { item.attributes.installed = ''; if (installedTask) { item.attributes.installed = - installedTask.attributes?.versions[0]?.version?.toString(); + installedTask.attributes?.versions[0]?.version?.toString() ?? + installedTask.attributes?.installed?.toString(); } } - item.cta.callback = ({ selectedVersion }) => { + item.cta.callback = async ({ selectedVersion }) => { + let selectedVersionUrl = getSelectedVersionUrl(item, selectedVersion); + if (isArtifactHubTask(item) && !selectedVersionUrl) { + try { + const details = await getArtifactHubTaskDetails( + item, + selectedVersion, + isDevConsoleProxyAvailable, + ); + selectedVersionUrl = details.content_url; + } catch (err) { + console.warn('Error fetching ArtifactHub task details:', err); + return; + } + } + return new Promise((resolve) => { if (!isArtifactHubTask(item)) { - if (item.provider === TaskProviders.tektonHub) { - const selectedVersionUrl = getSelectedVersionUrl( - item, - selectedVersion, - ); - if (installedTask) { - if (selectedVersion === item.attributes.installed) { - resolve(savedCallback.current(installedTask.data)); - } else { - resolve( - savedCallback.current({ metadata: { name: item.data.name } }), - ); - updateTask( - selectedVersionUrl, - installedTask, - namespace, - item.data.name, - ).catch(() => setFailedTasks([...failedTasks, item.data.name])); - } - } else { - handleTaskCreationWithNameConflict( - item.data.name, - (taskNameToUse) => - createTask(selectedVersionUrl, namespace, taskNameToUse), - resolve, - ); - } - } else { - resolve(savedCallback.current(item.data)); - } + resolve(savedCallback.current(item.data)); + return; } if ( item.provider === TaskProviders.artifactHub && isArtifactHubTask(item) ) { - const selectedVersionUrl = getSelectedVersionUrl( - item, - selectedVersion, - ); if (installedTask) { if (selectedVersion === item.attributes.installed) { resolve(savedCallback.current(installedTask.data)); @@ -184,9 +165,13 @@ const Contents: React.FC< item.data.task.name, selectedVersion, isDevConsoleProxyAvailable, - ).catch(() => - setFailedTasks([...failedTasks, item.data.task.name]), - ); + ).catch((error) => { + console.warn( + 'Error updating ArtifactHub task - callsite PipelineQuickSearch:', + error, + ); + setFailedTasks([...failedTasks, item.data.task.name]); + }); } } else { handleTaskCreationWithNameConflict( diff --git a/src/components/task-quicksearch/PipelineQuickSearchDetails.tsx b/src/components/task-quicksearch/PipelineQuickSearchDetails.tsx index 2f85cefe..199f4f3d 100644 --- a/src/components/task-quicksearch/PipelineQuickSearchDetails.tsx +++ b/src/components/task-quicksearch/PipelineQuickSearchDetails.tsx @@ -25,15 +25,9 @@ import { isArtifactHubTask, isOneVersionInstalled, isTaskVersionInstalled, - isTektonHubTaskWithoutVersions, } from './pipeline-quicksearch-utils'; import PipelineQuickSearchTaskAlert from './PipelineQuickSearchTaskAlert'; import PipelineQuickSearchVersionDropdown from './PipelineQuickSearchVersionDropdown'; -import { - getHubUIPath, - getTektonHubTaskVersions, -} from '../catalog/apis/tektonHub'; -import { ExternalLink } from '../utils/link'; import { handleCta } from '../quick-search'; import { QuickSearchDetailsRendererProps } from '../quick-search/QuickSearchDetails'; import { FLAGS } from '../../types'; @@ -57,6 +51,9 @@ const PipelineQuickSearchDetails: React.FC = ({ const [hasInstalledVersion, setHasInstalledVersion] = React.useState( isOneVersionInstalled(selectedItem), ); + const [detailsLoaded, setDetailsLoaded] = React.useState( + !isArtifactHubTask(selectedItem), + ); const resetVersions = React.useCallback(() => { setVersions(selectedItem?.attributes?.versions ?? []); setSelectedVersion(selectedItem?.attributes?.installed ?? ''); @@ -67,20 +64,24 @@ const PipelineQuickSearchDetails: React.FC = ({ (key) => { setSelectedVersion(key); if (isArtifactHubTask(selectedItem)) { + setDetailsLoaded(false); getArtifactHubTaskDetails(selectedItem, key, isDevConsoleProxyAvailable) .then((item) => { selectedItem.attributes.versions = item.available_versions; selectedItem.attributes.selectedVersionContentUrl = item.content_url; + selectedItem.attributes.selectedVersionForContentUrl = key; selectedItem.tags = item.keywords; setVersions([...item.available_versions]); setHasInstalledVersion(isOneVersionInstalled(selectedItem)); + setDetailsLoaded(true); }) .catch((err) => { // eslint-disable-next-line no-console console.warn('Error while getting ArtifactHub Task details:', err); resetVersions(); + setDetailsLoaded(true); }); } }, @@ -89,37 +90,10 @@ const PipelineQuickSearchDetails: React.FC = ({ React.useEffect(() => { resetVersions(); - const mounted = true; - if ( - isTektonHubTaskWithoutVersions(selectedItem) && - !isArtifactHubTask(selectedItem) - ) { - const debouncedLoadVersions = debounce(async () => { - if (mounted) { - try { - const itemVersions = await getTektonHubTaskVersions( - selectedItem?.data?.id, - selectedItem?.attributes?.apiURL, - ); - - selectedItem.attributes.versions = itemVersions; - - if (mounted) { - setVersions([...itemVersions]); - setHasInstalledVersion(isOneVersionInstalled(selectedItem)); - } - } catch (err) { - if (mounted) { - resetVersions(); - } - console.log('failed to fetch versions:', err); // eslint-disable-line no-console - } - } - }, 10); - debouncedLoadVersions(); - } + let mounted = true; if (isArtifactHubTask(selectedItem)) { + setDetailsLoaded(false); const debouncedLoadDetails = debounce(async () => { if (mounted) { try { @@ -128,25 +102,33 @@ const PipelineQuickSearchDetails: React.FC = ({ undefined, isDevConsoleProxyAvailable, ); - selectedItem.attributes.versions = item.available_versions; - selectedItem.attributes.selectedVersionContentUrl = - item.content_url; - selectedItem.tags = item.keywords; if (mounted) { + selectedItem.attributes.versions = item.available_versions; + selectedItem.attributes.selectedVersionContentUrl = + item.content_url; + selectedItem.attributes.selectedVersionForContentUrl = + selectedItem.data?.task?.version?.toString(); + selectedItem.tags = item.keywords; setVersions([...item.available_versions]); setHasInstalledVersion(isOneVersionInstalled(selectedItem)); + setDetailsLoaded(true); } } catch (err) { if (mounted) { resetVersions(); + setDetailsLoaded(true); } } } }, 10); debouncedLoadDetails(); + } else { + setDetailsLoaded(true); } - // return () => (mounted = false); + return () => { + mounted = false; + }; }, [resetVersions, selectedItem]); React.useEffect(() => { @@ -159,18 +141,6 @@ const PipelineQuickSearchDetails: React.FC = ({ ); } }, [selectedItem]); - const loadedVersion = React.useMemo( - () => - versions?.find( - (version) => version.version?.toString() === selectedVersion, - ), - [selectedVersion, versions], - ); - - const hubLink = getHubUIPath( - loadedVersion?.hubURLPath, - selectedItem.attributes.uiURL, - ); return (
@@ -188,10 +158,10 @@ const PipelineQuickSearchDetails: React.FC = ({