Skip to content

Commit e00cb08

Browse files
fix: updated task creation logic in pipeline builder to support installation of tasks with name name
1 parent c4545f5 commit e00cb08

File tree

5 files changed

+135
-35
lines changed

5 files changed

+135
-35
lines changed

src/components/catalog/apis/artifactHub.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export const createArtifactHubTask = (
102102
namespace: string,
103103
version: string,
104104
isDevConsoleProxyAvailable?: boolean,
105+
customName?: string,
105106
) => {
106107
const fetchTask = async (): Promise<K8sResourceKind> => {
107108
if (isDevConsoleProxyAvailable) {
@@ -122,6 +123,9 @@ export const createArtifactHubTask = (
122123
return fetchTask()
123124
.then((task: K8sResourceKind) => {
124125
task.metadata.namespace = namespace;
126+
if (customName) {
127+
task.metadata.name = customName;
128+
}
125129
task.metadata.annotations = {
126130
...task.metadata.annotations,
127131
[TektonTaskAnnotation.installedFrom]: ARTIFACTHUB,
@@ -185,6 +189,7 @@ export const updateArtifactHubTask = async (
185189

186190
export const fetchArtifactHubTasks = async (
187191
query: string,
192+
// eslint-disable-next-line @typescript-eslint/no-inferrable-types
188193
limit: number = 20,
189194
): Promise<ArtifactHubTask[]> => {
190195
try {

src/components/pipeline-builder/PipelineBuilderForm.tsx

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { FormikProps } from 'formik';
99
import * as _ from 'lodash';
1010
import { useTranslation } from 'react-i18next';
11-
import { PipelineModel } from '../../models';
11+
import { PipelineModel, TaskModel } from '../../models';
1212
import {
1313
PipelineKind,
1414
PipelineTask,
@@ -42,7 +42,11 @@ import FormFooter from '../pipelines-details/multi-column-field/FormFooter';
4242
import { FlexForm, FormBody } from './form-utils';
4343
import SyncedEditorField from './SyncedEditorField';
4444
import PipelineQuickSearch from '../task-quicksearch/PipelineQuickSearch';
45-
import { useModal } from '@openshift-console/dynamic-plugin-sdk';
45+
import {
46+
k8sDelete,
47+
k8sGet,
48+
useModal,
49+
} from '@openshift-console/dynamic-plugin-sdk';
4650

4751
type PipelineBuilderFormProps = FormikProps<PipelineBuilderFormikValues> & {
4852
existingPipeline: PipelineKind;
@@ -105,7 +109,6 @@ const PipelineBuilderForm: React.FC<PipelineBuilderFormProps> = (props) => {
105109
const updateTasks = (changes: CleanupResults): void => {
106110
const { tasks, listTasks, finallyTasks, finallyListTasks, loadingTasks } =
107111
changes;
108-
109112
setFieldValue('formData', {
110113
...formData,
111114
tasks,
@@ -179,6 +182,58 @@ const PipelineBuilderForm: React.FC<PipelineBuilderFormProps> = (props) => {
179182
}
180183
}, [selectedTask]);
181184

185+
const handleRemoveTask = React.useCallback(
186+
async (taskName: string) => {
187+
setSelectedTask(null);
188+
try {
189+
const taskContent = await k8sGet({
190+
model: TaskModel,
191+
name: taskName,
192+
ns: namespace,
193+
});
194+
195+
const installedFrom =
196+
taskContent?.metadata?.annotations?.['openshift.io/installed-from'] ??
197+
taskContent?.metadata?.annotations?.['tekton.dev/installedFrom'] ??
198+
'';
199+
200+
/* Doing this will keep the ArtifactHub / operator task in the user namespace and can be accessed from CLI but remove it from UI
201+
ArtifactHub (or artifacthub) — for tasks installed from Artifact Hub
202+
Operator or operator — for tasks installed by the Tekton/OpenShift Operator */
203+
const customTaskExists =
204+
!!taskContent &&
205+
taskContent.metadata?.name?.toLowerCase() ===
206+
taskName.toLowerCase() &&
207+
!['artifacthub', 'operator'].includes(installedFrom?.toLowerCase());
208+
209+
if (customTaskExists) {
210+
await k8sDelete({
211+
model: TaskModel,
212+
resource: { metadata: { name: taskName, namespace } },
213+
});
214+
}
215+
} catch (error) {
216+
if (error?.code === 404) {
217+
console.warn(
218+
`Task ${taskName} does not exist, safe to remove from builder`,
219+
error,
220+
);
221+
} else {
222+
console.error('Error fetching Task to delete:', error);
223+
}
224+
} finally {
225+
updateTasks(
226+
applyChange(
227+
taskGroup,
228+
{ type: UpdateOperationType.REMOVE_TASK, data: { taskName } },
229+
namespace,
230+
),
231+
);
232+
}
233+
},
234+
[namespace, taskGroup, updateTasks],
235+
);
236+
182237
return (
183238
<Drawer isExpanded={!!selectedTask} position="right">
184239
<DrawerContent
@@ -206,19 +261,7 @@ const PipelineBuilderForm: React.FC<PipelineBuilderFormProps> = (props) => {
206261
onRemoveTask={(taskName: string) => {
207262
launchModal(RemoveTaskModal, {
208263
taskName,
209-
onRemove: () => {
210-
setSelectedTask(null);
211-
updateTasks(
212-
applyChange(
213-
taskGroup,
214-
{
215-
type: UpdateOperationType.REMOVE_TASK,
216-
data: { taskName },
217-
},
218-
namespace,
219-
),
220-
);
221-
},
264+
onRemove: () => handleRemoveTask(taskName),
222265
});
223266
}}
224267
selectedData={selectedTask}

src/components/quick-search/utils/quick-search-utils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const handleCta = async (
2020
closeModal();
2121
await callback({
2222
...callbackProps,
23-
selectedVersion: item.data?.version,
23+
selectedVersion: item.data?.version ?? item.data?.task?.version,
2424
selectedItem: item,
2525
});
2626
removeQueryArgument('catalogSearch');

src/components/task-quicksearch/PipelineQuickSearch.tsx

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
TaskProviders,
2121
updateTask,
2222
} from './pipeline-quicksearch-utils';
23+
import { safeName } from '../pipeline-builder/utils';
2324
import PipelineQuickSearchDetails from './PipelineQuickSearchDetails';
2425
import { CatalogServiceProvider } from '../catalog/service';
2526
import { CatalogService } from '../catalog/types';
@@ -64,6 +65,51 @@ const Contents: React.FC<
6465
useLoadingTaskCleanup(onUpdateTasks, taskGroup);
6566
useCleanupOnFailure(failedTasks, onUpdateTasks, taskGroup);
6667

68+
// Get all existing task names from taskGroup and installed tasks
69+
const getExistingTaskNames = (): string[] => {
70+
const taskNames = new Set<string>();
71+
[
72+
...taskGroup.tasks,
73+
...taskGroup.finallyTasks,
74+
...taskGroup.listTasks,
75+
...taskGroup.loadingTasks,
76+
...taskGroup.finallyListTasks,
77+
].forEach((t) => {
78+
if (t?.name) taskNames.add(t.name);
79+
});
80+
81+
// Add installed catalog items (avoid duplicates)
82+
catalogService.items.forEach((catalogItem) => {
83+
const name = catalogItem.data?.metadata?.name;
84+
if (name) taskNames.add(name);
85+
});
86+
return Array.from(taskNames);
87+
};
88+
89+
const handleTaskCreationWithNameConflict = (
90+
taskName: string,
91+
createTaskFn: (taskNameToUse?: string) => Promise<any>,
92+
resolve: (value: any) => void,
93+
) => {
94+
// Checking if task with same name already exists, if yes then create with a different name to avoid conflict
95+
const existingTaskNames = getExistingTaskNames();
96+
if (existingTaskNames.includes(taskName)) {
97+
const taskNameToUse = safeName(existingTaskNames, taskName);
98+
createTaskFn(taskNameToUse)
99+
.then(() =>
100+
resolve(
101+
savedCallback.current({
102+
metadata: { name: taskNameToUse },
103+
}),
104+
),
105+
)
106+
.catch(() => setFailedTasks([...failedTasks, taskNameToUse]));
107+
} else {
108+
resolve(savedCallback.current({ metadata: { name: taskName } }));
109+
createTaskFn().catch(() => setFailedTasks([...failedTasks, taskName]));
110+
}
111+
};
112+
67113
const catalogServiceItems = catalogService.items.reduce((acc, item) => {
68114
const installedTask = findInstalledTask(catalogService.items, item);
69115

@@ -102,11 +148,11 @@ const Contents: React.FC<
102148
).catch(() => setFailedTasks([...failedTasks, item.data.name]));
103149
}
104150
} else {
105-
resolve(
106-
savedCallback.current({ metadata: { name: item.data.name } }),
107-
);
108-
createTask(selectedVersionUrl, namespace).catch(() =>
109-
setFailedTasks([...failedTasks, item.data.name]),
151+
handleTaskCreationWithNameConflict(
152+
item.data.name,
153+
(taskNameToUse) =>
154+
createTask(selectedVersionUrl, namespace, taskNameToUse),
155+
resolve,
110156
);
111157
}
112158
} else {
@@ -143,18 +189,17 @@ const Contents: React.FC<
143189
);
144190
}
145191
} else {
146-
resolve(
147-
savedCallback.current({
148-
metadata: { name: item.data.task.name },
149-
}),
150-
);
151-
createArtifactHubTask(
152-
selectedVersionUrl,
153-
namespace,
154-
selectedVersion,
155-
isDevConsoleProxyAvailable,
156-
).catch(() =>
157-
setFailedTasks([...failedTasks, item.data.task.name]),
192+
handleTaskCreationWithNameConflict(
193+
item.data.task.name,
194+
(taskNameToUse) =>
195+
createArtifactHubTask(
196+
selectedVersionUrl,
197+
namespace,
198+
selectedVersion,
199+
isDevConsoleProxyAvailable,
200+
taskNameToUse,
201+
),
202+
resolve,
158203
);
159204
}
160205
}

src/components/task-quicksearch/pipeline-quicksearch-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,19 @@ export const updateTask = async (
174174
});
175175
};
176176

177-
export const createTask = (url: string, namespace: string) => {
177+
export const createTask = (
178+
url: string,
179+
namespace: string,
180+
customName?: string,
181+
) => {
178182
return consoleFetch(url)
179183
.then(async (res) => {
180184
const yaml = await res.text();
181185
const task = load(yaml) as TaskKind;
182186
task.metadata.namespace = namespace;
187+
if (customName) {
188+
task.metadata.name = customName;
189+
}
183190
task.metadata.annotations = {
184191
...task.metadata.annotations,
185192
[TektonTaskAnnotation.installedFrom]: TEKTONHUB,

0 commit comments

Comments
 (0)