diff --git a/src/api/customRuns.js b/src/api/customRuns.js
index e5fc8c1fa..bf71697c1 100644
--- a/src/api/customRuns.js
+++ b/src/api/customRuns.js
@@ -18,7 +18,6 @@ import { deleteRequest, get, patch, post } from './comms';
import {
getQueryParams,
getTektonAPI,
- getTektonPipelinesAPIVersion,
removeSystemAnnotations,
removeSystemLabels,
useCollection,
@@ -43,14 +42,11 @@ function getCustomRunsAPI({ filters, isWebSocket, name, namespace }) {
}
export function getCustomRunPayload({
- kind,
labels,
namespace,
- nodeSelector,
params,
serviceAccount,
- customName,
- customRunName = `${customName ? `${customName}-run` : 'run'}-${Date.now()}`,
+ customRunName = `run-${Date.now()}`,
timeout
}) {
const payload = {
@@ -63,7 +59,7 @@ export function getCustomRunPayload({
spec: {
customRef: {
apiVersion: '',
- kind: 'Custom'
+ kind: ''
}
}
};
@@ -76,9 +72,6 @@ export function getCustomRunPayload({
value: params[name]
}));
}
- if (nodeSelector) {
- payload.spec.podTemplate = { nodeSelector };
- }
if (serviceAccount) {
payload.spec.serviceAccountName = serviceAccount;
}
@@ -158,32 +151,6 @@ export function rerunCustomRun(run) {
return post(uri, payload).then(({ body }) => body);
}
-export function createCustomRun({
- kind,
- labels,
- namespace,
- nodeSelector,
- params,
- serviceAccount,
- customName,
- customRunName = `${customName}-run-${Date.now()}`,
- timeout
-}) {
- const payload = getCustomRunPayload({
- kind,
- labels,
- namespace,
- nodeSelector,
- params,
- serviceAccount,
- customName,
- customRunName,
- timeout
- });
- const uri = getTektonAPI('customruns', { namespace });
- return post(uri, payload).then(({ body }) => body);
-}
-
export function createCustomRunRaw({ namespace, payload }) {
const uri = getTektonAPI('customruns', { namespace, version: 'v1beta1' });
return post(uri, payload).then(({ body }) => body);
@@ -194,8 +161,7 @@ export function generateNewCustomRunPayload({ customRun, rerun }) {
customRun.metadata;
const payload = deepClone(customRun);
- payload.apiVersion =
- payload.apiVersion || 'tekton.dev/v1beta1';
+ payload.apiVersion = payload.apiVersion || 'tekton.dev/v1beta1';
payload.kind = payload.kind || 'CustomRun';
function getGenerateName() {
diff --git a/src/containers/CreateCustomRun/CreateCustomRun.jsx b/src/containers/CreateCustomRun/CreateCustomRun.jsx
index 76ccb3c44..04485c17c 100644
--- a/src/containers/CreateCustomRun/CreateCustomRun.jsx
+++ b/src/containers/CreateCustomRun/CreateCustomRun.jsx
@@ -14,59 +14,31 @@ limitations under the License.
import React, { Suspense, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
-import keyBy from 'lodash.keyby';
import yaml from 'js-yaml';
import {
ALL_NAMESPACES,
- generateId,
- getTranslateWithId,
- resourceNameRegex,
urls,
useTitleSync
} from '@tektoncd/dashboard-utils';
-import { KeyValueList, Loading } from '@tektoncd/dashboard-components';
+import { Loading } from '@tektoncd/dashboard-components';
import { useIntl } from 'react-intl';
import {
- createCustomRun,
createCustomRunRaw,
generateNewCustomRunPayload,
getCustomRunPayload,
useSelectedNamespace,
- useTaskByKind,
useCustomRun
} from '../../api';
-import { isValidLabel } from '../../utils';
const YAMLEditor = React.lazy(() => import('../YAMLEditor'));
const initialState = {
creating: false,
- customRef: '',
- customRunName: '',
- customSpec: '',
- invalidLabels: {},
kind: 'CustomRun',
labels: [],
- namespace: '',
params: {},
- paramSpecs: [],
- retries: '',
- serviceAccountName: '',
- submitError: '',
- timeout: '',
validationError: false,
- validCustomRunName: true,
- workspaces: ''
-};
-
-const initialParamsState = paramSpecs => {
- if (!paramSpecs) {
- return {};
- }
- return paramSpecs.reduce(
- (acc, param) => ({ ...acc, [param.name]: param.default || '' }),
- {}
- );
+ validCustomRunName: true
};
const itemToString = ({ text }) => text;
@@ -77,14 +49,6 @@ function CreateCustomRun() {
const navigate = useNavigate();
const { selectedNamespace: defaultNamespace } = useSelectedNamespace();
- function getCustomDetails() {
- const urlSearchParams = new URLSearchParams(location.search);
- return {
- kind: urlSearchParams.get('kind') || 'Custom',
- customName: urlSearchParams.get('customName') || ''
- };
- }
-
function getCustomRunName() {
const urlSearchParams = new URLSearchParams(location.search);
return urlSearchParams.get('customRunName') || '';
@@ -98,48 +62,21 @@ function CreateCustomRun() {
);
}
- function isYAMLMode() {
- const urlSearchParams = new URLSearchParams(location.search);
- return urlSearchParams.get('mode') === 'yaml';
- }
-
- const { kind: initialCustomKind, customName: customRefFromDetails } =
- getCustomDetails();
const [
{
- creating,
- customRef,
- customRunName,
- customSpec,
- invalidLabels,
kind,
labels,
namespace,
- params,
- retries,
- serviceAccountName,
- submitError,
- timeout,
- validationError,
- validCustomRunName,
- workspaces
+ params
},
setState
] = useState({
...initialState,
- kind: initialCustomKind || 'Custom',
+ kind: 'Custom',
namespace: getNamespace(),
- customRef: customRefFromDetails,
- params: initialParamsState(null)
+ customRef: '',
});
- const { data: custom, error: customError } = useTaskByKind(
- { kind, name: customRef, namespace },
- { enabled: !!customRef }
- );
-
- const paramSpecs = custom?.spec?.params;
-
useTitleSync({
page: intl.formatMessage({
id: 'dashboard.createCustomRun.title',
@@ -147,129 +84,6 @@ function CreateCustomRun() {
})
});
- function switchToYamlMode() {
- const queryParams = new URLSearchParams(location.search);
- queryParams.set('mode', 'yaml');
- const browserURL = location.pathname.concat(`?${queryParams.toString()}`);
- navigate(browserURL);
- }
-
- function checkFormValidation() {
- // Namespace, customRef, and Params must all have values
- const validNamespace = !!namespace;
- const validCustomRef = !!customRef;
-
- const paramSpecMap = keyBy(paramSpecs, 'name');
- const validParams =
- !params ||
- Object.keys(params).reduce(
- (acc, name) =>
- acc &&
- (!!params[name] ||
- typeof paramSpecMap[name]?.default !== 'undefined'),
- true
- );
-
- // CustomRun name
- const customRunNameTest =
- !customRunName ||
- (resourceNameRegex.test(customRunName) && customRunName.length < 64);
- setState(state => ({ ...state, validCustomRunName: customRunNameTest }));
-
- // Labels
- let validLabels = true;
- labels.forEach(label => {
- ['key', 'value'].forEach(type => {
- if (!isValidLabel(type, label[type])) {
- validLabels = false;
- setState(prevState => ({
- ...prevState,
- invalidLabels: {
- ...prevState.invalidLabels,
- [`${label.id}-${type}`]: true
- }
- }));
- }
- });
- });
-
- return (
- validNamespace &&
- validCustomRef &&
- validParams &&
- validLabels &&
- customRunNameTest
- );
- }
-
- function handleClose() {
- const { kind: customKind, customName } = getCustomDetails();
- let url = urls.customRuns.all();
- if (customName && namespace && namespace !== ALL_NAMESPACES) {
- url = urls.customRuns[
- customKind === 'ClusterTask' ? 'byClusterTask' : 'byTask'
- ]({
- namespace,
- customName
- });
- } else if (namespace && namespace !== ALL_NAMESPACES) {
- url = urls.customRuns.byNamespace({ namespace });
- }
- navigate(url);
- }
-
- function handleAddLabel(prop) {
- setState(prevState => ({
- ...prevState,
- [prop]: [
- ...prevState[prop],
- {
- id: generateId(`label${prevState[prop].length}-`),
- key: '',
- keyPlaceholder: 'key',
- value: '',
- valuePlaceholder: 'value'
- }
- ]
- }));
- }
-
- function handleRemoveLabel(prop, invalidProp, index) {
- setState(prevState => {
- const newLabels = [...prevState[prop]];
- const newInvalidLabels = { ...prevState[invalidProp] };
- const removedLabel = newLabels[index];
- newLabels.splice(index, 1);
- if (removedLabel.id in newInvalidLabels) {
- delete newInvalidLabels[`${removedLabel.id}-key`];
- delete newInvalidLabels[`${removedLabel.id}-value`];
- }
- return {
- ...prevState,
- [prop]: newLabels,
- [invalidProp]: newInvalidLabels
- };
- });
- }
-
- function handleChangeLabel(prop, invalidProp, { type, index, value }) {
- setState(prevState => {
- const newLabels = [...prevState[prop]];
- newLabels[index][type] = value;
- const newInvalidLabels = { ...prevState[invalidProp] };
- if (!isValidLabel(type, value)) {
- newInvalidLabels[`${newLabels[index].id}-${type}`] = true;
- } else {
- delete newInvalidLabels[`${newLabels[index].id}-${type}`];
- }
- return {
- ...prevState,
- [prop]: newLabels,
- [invalidProp]: newInvalidLabels
- };
- });
- }
-
function handleCloseYAMLEditor() {
let url = urls.customRuns.all();
if (defaultNamespace && defaultNamespace !== ALL_NAMESPACES) {
@@ -288,132 +102,6 @@ function CreateCustomRun() {
});
}
- function handleNamespaceChange({ selectedItem }) {
- const { text = '' } = selectedItem || {};
- if (text !== namespace) {
- setState(state => ({
- ...state,
- ...initialState,
- kind: state.kind,
- namespace: text
- }));
-
- const queryParams = new URLSearchParams(location.search);
- if (text) {
- queryParams.set('namespace', text);
- } else {
- queryParams.delete('namespace');
- }
- queryParams.delete('customName');
- const browserURL = location.pathname.concat(`?${queryParams.toString()}`);
- navigate(browserURL);
- }
- }
-
- function handleKindChange({ selectedItem }) {
- const { text = '' } = selectedItem || {};
- if (text !== kind) {
- setState(state => ({
- ...state,
- ...initialState,
- kind: text
- }));
-
- const queryParams = new URLSearchParams(location.search);
- queryParams.set('kind', text);
- queryParams.delete('namespace');
- queryParams.delete('customName');
- const browserURL = location.pathname.concat(`?${queryParams.toString()}`);
- navigate(browserURL);
- }
- }
-
- function handleParamChange(key, value) {
- setState(state => ({
- ...state,
- params: {
- ...state.params,
- [key]: value
- }
- }));
- }
-
- function handleCustomChange({ selectedItem }) {
- const { text } = selectedItem || {};
-
- const queryParams = new URLSearchParams(location.search);
- if (text) {
- queryParams.set('customName', text);
- } else {
- queryParams.delete('customName');
- }
- const browserURL = location.pathname.concat(`?${queryParams.toString()}`);
- navigate(browserURL);
-
- if (text && text !== customRef) {
- setState(state => {
- return {
- ...state,
- customRef: text,
- params: initialParamsState(paramSpecs)
- };
- });
- return;
- }
- // Reset params when no Task is selected
- setState(state => ({
- ...state,
- ...initialState,
- namespace: state.namespace
- }));
- }
-
- function handleSubmit(event) {
- event.preventDefault();
-
- // Check form validation
- const valid = checkFormValidation();
- setState(state => ({ ...state, validationError: !valid }));
- if (!valid) {
- return;
- }
-
- setState(state => ({ ...state, creating: true }));
-
- createCustomRun({
- customName: customRef,
- customRunName: customRunName || undefined,
- kind,
- labels: labels.reduce((acc, { key, value }) => {
- acc[key] = value;
- return acc;
- }, {}),
- namespace,
- params,
- retries,
- serviceAccountName,
- timeout,
- workspaces
- })
- .then(() => {
- navigate(urls.customRuns.byNamespace({ namespace }));
- })
- .catch(error => {
- error.response.text().then(text => {
- const statusCode = error.response.status;
- let errorMessage = `error code ${statusCode}`;
- if (text) {
- errorMessage = `${text} (error code ${statusCode})`;
- }
- setState(state => ({
- ...state,
- creating: false,
- submitError: errorMessage
- }));
- });
- });
- }
-
const externalCustomRunName = getCustomRunName();
if (externalCustomRunName) {
const { data: customRunObject, isLoading } = useCustomRun(
@@ -454,19 +142,13 @@ function CreateCustomRun() {
}
const customRun = getCustomRunPayload({
- customName: customRef,
- customRunName: customRunName || undefined,
kind,
labels: labels.reduce((acc, { key, value }) => {
acc[key] = value;
return acc;
}, {}),
namespace,
- params,
- retries,
- serviceAccountName,
- timeout,
- workspaces
+ params
});
return (
diff --git a/src/containers/CreateCustomRun/CreateCustomRun.test.jsx b/src/containers/CreateCustomRun/CreateCustomRun.test.jsx
new file mode 100644
index 000000000..face2f5f6
--- /dev/null
+++ b/src/containers/CreateCustomRun/CreateCustomRun.test.jsx
@@ -0,0 +1,161 @@
+/*
+Copyright 2023 The Tekton Authors
+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 React from 'react';
+import { fireEvent, waitFor } from '@testing-library/react';
+
+import { renderWithRouter } from '../../utils/test';
+
+import CreateCustomRun from './CreateCustomRun';
+import * as APIUtils from '../../api/utils';
+import * as CustomRunsAPI from '../../api/customRuns';
+
+const submitButton = allByText => allByText('Create')[0];
+
+const customRunRawGenerateName = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'CustomRun',
+ metadata: {
+ annotations: {},
+ generateName: 'test-custom-run-name-',
+ labels: {},
+ namespace: 'test-namespace'
+ },
+ spec: {
+ customRef: {
+ apiVersion: 'example.dev/v1beta1'
+ }
+ }
+};
+
+const expectedCustomRun = `apiVersion: tekton.dev/v1beta1
+kind: CustomRun
+metadata:
+ name: run-1111111111
+ namespace: test-namespace
+ labels: {}
+spec:
+ customRef:
+ apiVersion: ''
+ kind: ''
+ params: []`;
+
+const expectedCustomRunOneLine = expectedCustomRun.replace(/\r?\n|\r/g, '');
+
+const findNameRegexp = /name: run-\S+/;
+
+describe('CreateCustomRun yaml mode', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.spyOn(window.history, 'pushState');
+
+ // Workaround for codemirror vs jsdom https://github.com/jsdom/jsdom/issues/3002#issuecomment-1118039915
+ // for textRange(...).getClientRects is not a function
+ Range.prototype.getBoundingClientRect = () => ({
+ bottom: 0,
+ height: 0,
+ left: 0,
+ right: 0,
+ top: 0,
+ width: 0
+ });
+ Range.prototype.getClientRects = () => ({
+ item: () => null,
+ length: 0,
+ [Symbol.iterator]: vi.fn()
+ });
+ });
+
+ it('renders with namespace', async () => {
+ vi.spyOn(CustomRunsAPI, 'createCustomRunRaw').mockImplementation(() =>
+ Promise.resolve({ data: {} })
+ );
+ vi.spyOn(CustomRunsAPI, 'useCustomRun').mockImplementation(() => ({
+ data: customRunRawGenerateName
+ }));
+
+ const { getByRole, queryAllByText } = renderWithRouter(
+ ,
+ {
+ path: '/customruns/create',
+ route: '/customruns/create?mode=yaml&namespace=test-namespace'
+ }
+ );
+
+ await waitFor(
+ () => {
+ expect(queryAllByText(/Loading/).length).toBe(0);
+ },
+ {
+ timeout: 3000
+ }
+ );
+ // await waitFor(() => {
+ // expect(queryAllByText(/Loading/).length).toBe(0);
+ // });
+ await waitFor(() => {
+ expect(getByRole(/textbox/)).toBeTruthy();
+ });
+ let actual = getByRole(/textbox/).textContent;
+ actual = actual.replace(findNameRegexp, 'name: run-1111111111');
+ expect(actual.trim()).toEqual(expectedCustomRunOneLine);
+ });
+
+ it('handle submit with customrun and namespace', async () => {
+ vi.spyOn(CustomRunsAPI, 'createCustomRunRaw').mockImplementation(() =>
+ Promise.resolve({ data: {} })
+ );
+ vi.spyOn(CustomRunsAPI, 'useCustomRun').mockImplementation(() => ({
+ data: customRunRawGenerateName
+ }));
+
+ const { queryAllByText } = renderWithRouter(, {
+ path: '/customruns/create',
+ route:
+ '/customruns/create?mode=yaml&customRunName=test-custom-run-name&namespace=test-namespace'
+ });
+
+ await waitFor(
+ () => {
+ expect(queryAllByText(/Loading/).length).toBe(0);
+ },
+ { timeout: 3000 }
+ );
+ expect(submitButton(queryAllByText)).toBeTruthy();
+
+ fireEvent.click(submitButton(queryAllByText));
+
+ await waitFor(() => {
+ expect(CustomRunsAPI.createCustomRunRaw).toHaveBeenCalledTimes(1);
+ });
+ expect(CustomRunsAPI.createCustomRunRaw).toHaveBeenCalledWith(
+ expect.objectContaining({
+ namespace: 'test-namespace',
+ payload: customRunRawGenerateName
+ })
+ );
+ await waitFor(() => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ it('handles onClose event', () => {
+ vi.spyOn(APIUtils, 'useSelectedNamespace')
+ .mockImplementation(() => ({ selectedNamespace: 'namespace-1' }));
+ vi.spyOn(window.history, 'pushState');
+ const { getByText } = renderWithRouter();
+ fireEvent.click(getByText(/cancel/i));
+ // will be called once for render (from test utils) and once on navigation
+ expect(window.history.pushState).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/containers/CustomRuns/CustomRuns.jsx b/src/containers/CustomRuns/CustomRuns.jsx
index 90b7c7d8e..729ed8429 100644
--- a/src/containers/CustomRuns/CustomRuns.jsx
+++ b/src/containers/CustomRuns/CustomRuns.jsx
@@ -21,7 +21,6 @@ import {
ALL_NAMESPACES,
getFilters,
getStatus,
- labels,
urls,
useTitleSync
} from '@tektoncd/dashboard-utils';
@@ -114,11 +113,6 @@ function CustomRuns() {
const params = useParams();
const filters = getFilters(location);
- const customFilter = filters.find(f => f.indexOf(`${CUSTOM}=`) !== -1) || '';
- const customName = customFilter.replace(`${labels.CUSTOM}=`, '');
-
- const kind = 'Custom';
-
useTitleSync({ page: 'CustomRuns' });
const { selectedNamespace } = useSelectedNamespace();
@@ -250,7 +244,7 @@ function CustomRuns() {
},
{
actionText: intl.formatMessage({
- id: 'dashboard.cancelCustomRun.actionText',
+ id: 'dashboard.cancelTaskRun.actionText',
defaultMessage: 'Stop'
}),
action: cancel,
@@ -322,16 +316,12 @@ function CustomRuns() {
: [
{
onClick: () => {
- let queryString;
- if (namespace !== ALL_NAMESPACES) {
- queryString = new URLSearchParams({
- ...(namespace !== ALL_NAMESPACES && { namespace })
- }).toString();
- }
- navigate(
+ const queryString = new URLSearchParams({
+ ...(namespace !== ALL_NAMESPACES && { namespace }),
// currently default is yaml mode
- urls.customRuns.create() + (queryString ? `?${queryString}&mode=yaml` : '?mode=yaml')
- );
+ mode: 'yaml'
+ }).toString();
+ navigate(urls.customRuns.create() + `?${queryString}`);
},
text: intl.formatMessage({
id: 'dashboard.actions.createButton',
diff --git a/src/nls/messages_de.json b/src/nls/messages_de.json
index 0aa8bf0e4..3358ad5d8 100644
--- a/src/nls/messages_de.json
+++ b/src/nls/messages_de.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_en.json b/src/nls/messages_en.json
index cb5b98e5e..396077389 100644
--- a/src/nls/messages_en.json
+++ b/src/nls/messages_en.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "Select ClusterTask",
"dashboard.clusterTriggerBinding.noParams": "No parameters found for this ClusterTriggerBinding.",
"dashboard.create.yamlModeButton": "YAML Mode",
+ "dashboard.createCustomRun.title": "Create CustomRun",
"dashboard.createPipelineRun.createError": "Error creating PipelineRun",
"dashboard.createPipelineRun.disabled": "Disabled",
"dashboard.createPipelineRun.enabled": "Enabled",
diff --git a/src/nls/messages_es.json b/src/nls/messages_es.json
index 06357b1f3..32d3a69e5 100644
--- a/src/nls/messages_es.json
+++ b/src/nls/messages_es.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_fr.json b/src/nls/messages_fr.json
index f7a9156c2..fcf1b5a85 100644
--- a/src/nls/messages_fr.json
+++ b/src/nls/messages_fr.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_it.json b/src/nls/messages_it.json
index 80dd3e593..bf61ae540 100644
--- a/src/nls/messages_it.json
+++ b/src/nls/messages_it.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_ja.json b/src/nls/messages_ja.json
index 350130280..826c07618 100644
--- a/src/nls/messages_ja.json
+++ b/src/nls/messages_ja.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "ClusterTaskを選択",
"dashboard.clusterTriggerBinding.noParams": "このClusterTriggerBindingのパラメータが見つかりません",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "PipelineRunの作成中にエラーが発生しました",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_ko.json b/src/nls/messages_ko.json
index 551b21279..db08a36b8 100644
--- a/src/nls/messages_ko.json
+++ b/src/nls/messages_ko.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "ClusterTask 선택",
"dashboard.clusterTriggerBinding.noParams": "이 ClusterTriggerBinding에 대한 매개변수를 찾을 수 없습니다.",
"dashboard.create.yamlModeButton": "YAML 모드",
+ "dashboard.createCustomRun.title": "CustomRun 만들기",
"dashboard.createPipelineRun.createError": "PipelineRun 생성 오류",
"dashboard.createPipelineRun.disabled": "비활성화",
"dashboard.createPipelineRun.enabled": "활성화",
diff --git a/src/nls/messages_pt.json b/src/nls/messages_pt.json
index 98cade6c9..62d2d7721 100644
--- a/src/nls/messages_pt.json
+++ b/src/nls/messages_pt.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",
diff --git a/src/nls/messages_zh-Hans.json b/src/nls/messages_zh-Hans.json
index 59e475644..c3777c612 100644
--- a/src/nls/messages_zh-Hans.json
+++ b/src/nls/messages_zh-Hans.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "选择 ClusterTask",
"dashboard.clusterTriggerBinding.noParams": "未找到该 ClusterTriggerBinding 的参数。",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "创建 PipelineRun 时失败",
"dashboard.createPipelineRun.disabled": "禁用",
"dashboard.createPipelineRun.enabled": "启用",
diff --git a/src/nls/messages_zh-Hant.json b/src/nls/messages_zh-Hant.json
index ad49b137d..d24773a4f 100644
--- a/src/nls/messages_zh-Hant.json
+++ b/src/nls/messages_zh-Hant.json
@@ -49,6 +49,7 @@
"dashboard.clusterTasksDropdown.label": "",
"dashboard.clusterTriggerBinding.noParams": "",
"dashboard.create.yamlModeButton": "",
+ "dashboard.createCustomRun.title": "",
"dashboard.createPipelineRun.createError": "",
"dashboard.createPipelineRun.disabled": "",
"dashboard.createPipelineRun.enabled": "",