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": "",