From 57d42b04c3d4d4aabb1f893465b2e0ae29857e8a Mon Sep 17 00:00:00 2001
From: Marniks7 <9289172+marniks7@users.noreply.github.com>
Date: Sun, 11 Dec 2022 21:39:17 -0500
Subject: [PATCH] Add new action for PipelineRun - Edit and Run
---
.../cypress/e2e/run/actions-pipelinerun.cy.js | 78 ++++++
packages/utils/src/utils/index.js | 8 +
src/api/pipelineRuns.js | 72 ++++--
src/api/pipelineRuns.test.js | 225 ++++++++++++++++++
.../CreatePipelineRun/CreatePipelineRun.js | 39 ++-
.../CreatePipelineRun.test.js | 109 ++++++++-
.../CreatePipelineRun/YAMLEditor.js | 53 +++--
.../CreatePipelineRun/YAMLEditor.test.js | 14 ++
src/containers/PipelineRun/PipelineRun.js | 15 ++
src/containers/PipelineRuns/PipelineRuns.js | 15 ++
src/nls/messages_de.json | 2 +
src/nls/messages_en.json | 2 +
src/nls/messages_es.json | 2 +
src/nls/messages_fr.json | 2 +
src/nls/messages_it.json | 2 +
src/nls/messages_ja.json | 2 +
src/nls/messages_ko.json | 2 +
src/nls/messages_pt.json | 2 +
src/nls/messages_zh-Hans.json | 2 +
src/nls/messages_zh-Hant.json | 2 +
20 files changed, 603 insertions(+), 45 deletions(-)
create mode 100644 packages/e2e/cypress/e2e/run/actions-pipelinerun.cy.js
diff --git a/packages/e2e/cypress/e2e/run/actions-pipelinerun.cy.js b/packages/e2e/cypress/e2e/run/actions-pipelinerun.cy.js
new file mode 100644
index 0000000000..0afe862a3e
--- /dev/null
+++ b/packages/e2e/cypress/e2e/run/actions-pipelinerun.cy.js
@@ -0,0 +1,78 @@
+/*
+Copyright 2022 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.
+*/
+
+const namespace = 'e2e-actions';
+describe('Edit and run Pipeline Run', () => {
+ before(() => {
+ cy.exec('kubectl version --client');
+ cy.exec(`kubectl create namespace ${namespace} || true`);
+ });
+
+ after(() => {
+ cy.exec(`kubectl delete namespace ${namespace} || true`);
+ });
+
+ it('should create pipelinerun on edit and run', function () {
+ // given pipeline
+ const uniqueNumber = Date.now();
+ const pipelineName = `sp-${uniqueNumber}`;
+ const pipeline = `apiVersion: tekton.dev/v1beta1
+kind: Pipeline
+metadata:
+ name: ${pipelineName}
+ namespace: ${namespace}
+spec:
+ tasks:
+ - name: hello
+ taskSpec:
+ steps:
+ - name: echo
+ image: busybox
+ script: |
+ #!/bin/ash
+ echo "Hello World!"
+ `;
+ cy.exec(`echo "${pipeline}" | kubectl apply -f -`);
+ // when create first pipeline run
+ cy.visit(
+ `/#/pipelineruns/create?namespace=${namespace}&pipelineName=${pipelineName}`
+ );
+ cy.get('[id=create-pipelinerun--namespaces-dropdown]').should(
+ 'have.value',
+ namespace
+ );
+ cy.get('[id=create-pipelinerun--pipelines-dropdown]').should(
+ 'have.value',
+ pipelineName
+ );
+ cy.contains('button', 'Create').click();
+
+ cy.get(
+ `td:has(.bx--link[title*=${pipelineName}-run]) + td:has(.tkn--status[data-reason=Succeeded])`,
+ { timeout: 15000 }
+ ).should('have.length', 1);
+
+ // when edit and run to create second pipeline run
+ cy.contains(`${pipelineName}-run`).parent().click();
+ cy.contains('button', 'Actions').click();
+ cy.contains('button', 'Edit and run').click();
+ cy.get('.cm-content').contains(`name: ${pipelineName}`);
+ cy.contains('button', 'Create').click();
+
+ // then
+ cy.get(
+ `td:has(.bx--link[title*=${pipelineName}-run]) + td:has(.tkn--status[data-reason=Succeeded])`,
+ { timeout: 15000 }
+ ).should('have.length', 2);
+ });
+});
diff --git a/packages/utils/src/utils/index.js b/packages/utils/src/utils/index.js
index dc40740d45..66cf7ea43a 100644
--- a/packages/utils/src/utils/index.js
+++ b/packages/utils/src/utils/index.js
@@ -356,6 +356,14 @@ export function getGenerateNamePrefixForRerun(name) {
return `${root}${rerunIdentifier}`;
}
+export function getGenerateNamePrefixForNewRun(name) {
+ let generateName = name;
+ if (!name.endsWith('-')) {
+ generateName = `${name}-`;
+ }
+ return `${generateName}`;
+}
+
/*
getParams required to support 3rd-party consumers of certain dashboard
components (e.g. PipelineRun) while they migrate to the Tekton beta
diff --git a/src/api/pipelineRuns.js b/src/api/pipelineRuns.js
index bf7d0db77c..29f5859fa1 100644
--- a/src/api/pipelineRuns.js
+++ b/src/api/pipelineRuns.js
@@ -11,7 +11,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { getGenerateNamePrefixForRerun } from '@tektoncd/dashboard-utils';
+import {
+ getGenerateNamePrefixForNewRun,
+ getGenerateNamePrefixForRerun
+} from '@tektoncd/dashboard-utils';
import deepClone from 'lodash.clonedeep';
import { deleteRequest, get, patch, post } from './comms';
@@ -170,30 +173,46 @@ export function createPipelineRun({
return post(uri, payload).then(({ body }) => body);
}
-export function rerunPipelineRun(pipelineRun) {
- const { annotations, labels, name, namespace } = pipelineRun.metadata;
+export function generateNewPipelineRunPayload(pipelineRun, rerun) {
+ const { annotations, labels, name, namespace, generateName } =
+ pipelineRun.metadata;
const payload = deepClone(pipelineRun);
payload.apiVersion =
payload.apiVersion || `tekton.dev/${getTektonPipelinesAPIVersion()}`;
payload.kind = payload.kind || 'PipelineRun';
+
+ function getGenerateName() {
+ if (rerun) {
+ return getGenerateNamePrefixForRerun(name);
+ }
+
+ return generateName || getGenerateNamePrefixForNewRun(name);
+ }
+
payload.metadata = {
- annotations: annotations || {},
- generateName: getGenerateNamePrefixForRerun(name),
- labels: {
- ...labels,
- 'dashboard.tekton.dev/rerunOf': name
- },
+ annotations,
+ generateName: getGenerateName(),
+ labels,
namespace
};
+ if (rerun) {
+ payload.metadata.labels = {
+ ...labels,
+ 'dashboard.tekton.dev/rerunOf': name
+ };
+ }
- Object.keys(payload.metadata.labels).forEach(label => {
- if (label.startsWith('tekton.dev/')) {
- delete payload.metadata.labels[label];
- }
- });
+ if (payload.metadata.labels) {
+ Object.keys(payload.metadata.labels).forEach(label => {
+ if (label.startsWith('tekton.dev/')) {
+ delete payload.metadata.labels[label];
+ }
+ });
+ }
- /*
+ if (payload.metadata.annotations) {
+ /*
This is used by Tekton Pipelines as part of the conversion between v1beta1
and v1 resources. Creating a run with this in place prevents it from actually
executing and instead adopts the status of the original TaskRuns.
@@ -204,14 +223,27 @@ export function rerunPipelineRun(pipelineRun) {
When v1beta1 has been fully removed from Tekton Pipelines we can revisit this
and remove all remaining `tekton.dev/*` annotations.
- */
- delete payload.metadata.annotations['tekton.dev/v1beta1TaskRuns'];
- delete payload.metadata.annotations[
- 'kubectl.kubernetes.io/last-applied-configuration'
- ];
+ */
+ delete payload.metadata.annotations['tekton.dev/v1beta1TaskRuns'];
+ delete payload.metadata.annotations[
+ 'kubectl.kubernetes.io/last-applied-configuration'
+ ];
+ }
+ Object.keys(payload.metadata).forEach(
+ i => payload.metadata[i] === undefined && delete payload.metadata[i]
+ );
delete payload.status;
+
delete payload.spec?.status;
+ return { namespace, payload };
+}
+
+export function rerunPipelineRun(pipelineRun) {
+ const { namespace, payload } = generateNewPipelineRunPayload(
+ pipelineRun,
+ true
+ );
const uri = getTektonAPI('pipelineruns', { namespace });
return post(uri, payload).then(({ body }) => body);
diff --git a/src/api/pipelineRuns.test.js b/src/api/pipelineRuns.test.js
index 1be357ea6c..1eb805d810 100644
--- a/src/api/pipelineRuns.test.js
+++ b/src/api/pipelineRuns.test.js
@@ -11,9 +11,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import yaml from 'js-yaml';
import * as API from './pipelineRuns';
import * as utils from './utils';
import { rest, server } from '../../config_frontend/msw';
+import { generateNewPipelineRunPayload } from './pipelineRuns';
it('cancelPipelineRun', () => {
const name = 'foo';
@@ -326,3 +328,226 @@ it('startPipelineRun', () => {
}
);
});
+
+it('generate new pipeline run minimum for rerun', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ name: 'test',
+ namespace: 'test-namespace'
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ }
+ }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, true);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ generateName: test-r-
+ labels:
+ dashboard.tekton.dev/rerunOf: test
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
+
+it('generate new pipeline run maximum for rerun', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ name: 'test',
+ namespace: 'test-namespace',
+ annotations: {
+ keya: 'valuea',
+ 'kubectl.kubernetes.io/last-applied-configuration':
+ '{"apiVersion": "tekton.dev/v1beta1", "keya": "valuea"}'
+ },
+ labels: {
+ keyl: 'valuel',
+ key2: 'value2',
+ 'tekton.dev/pipeline': 'foo'
+ },
+ uid: '111-233-33',
+ resourceVersion: 'aaaa'
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ },
+ params: [{ name: 'param-1' }, { name: 'param-2' }]
+ },
+ status: { startTime: '0' }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, true);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ annotations:
+ keya: valuea
+ generateName: test-r-
+ labels:
+ keyl: valuel
+ key2: value2
+ dashboard.tekton.dev/rerunOf: test
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+ params:
+ - name: param-1
+ - name: param-2
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
+
+it('generate new pipeline run minimum', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ name: 'test',
+ namespace: 'test-namespace'
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ }
+ }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, false);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ generateName: test-
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
+it('generate new pipeline run maximum', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ annotations: {
+ keya: 'valuea',
+ 'kubectl.kubernetes.io/last-applied-configuration':
+ '{"apiVersion": "tekton.dev/v1beta1", "keya": "valuea"}'
+ },
+ labels: {
+ keyl: 'valuel',
+ key2: 'value2',
+ 'tekton.dev/pipeline': 'foo'
+ },
+ name: 'test',
+ namespace: 'test-namespace',
+ uid: '111-233-33',
+ resourceVersion: 'aaaa'
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ },
+ params: [{ name: 'param-1' }, { name: 'param-2' }]
+ },
+ status: { startTime: '0' }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, false);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ annotations:
+ keya: valuea
+ generateName: test-
+ labels:
+ keyl: valuel
+ key2: value2
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+ params:
+ - name: param-1
+ - name: param-2
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
+it('generate new pipeline run last applied configuration should be removed', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ annotations: {
+ 'kubectl.kubernetes.io/last-applied-configuration':
+ '{"apiVersion": "tekton.dev/v1beta1", "keya": "valuea"}'
+ },
+ name: 'test',
+ namespace: 'test-namespace'
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ }
+ }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, false);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ annotations: {}
+ generateName: test-
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
+
+it('generate new pipeline run tekton.dev labels should be removed', () => {
+ const pr = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ name: 'test',
+ namespace: 'test-namespace',
+ labels: {
+ 'tekton.dev/pipeline': 'foo',
+ 'tekton.dev/run': 'foo'
+ }
+ },
+ spec: {
+ pipelineRef: {
+ name: 'simple'
+ }
+ }
+ };
+ const { namespace, payload } = generateNewPipelineRunPayload(pr, false);
+ const expected = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ generateName: test-
+ labels: {}
+ namespace: test-namespace
+spec:
+ pipelineRef:
+ name: simple
+`;
+ expect(namespace).toEqual('test-namespace');
+ expect(yaml.dump(payload)).toEqual(expected);
+});
diff --git a/src/containers/CreatePipelineRun/CreatePipelineRun.js b/src/containers/CreatePipelineRun/CreatePipelineRun.js
index 3ca70e9fef..0bae70e01d 100644
--- a/src/containers/CreatePipelineRun/CreatePipelineRun.js
+++ b/src/containers/CreatePipelineRun/CreatePipelineRun.js
@@ -40,8 +40,10 @@ import {
} from '..';
import {
createPipelineRun,
+ generateNewPipelineRunPayload,
getPipelineRunPayload,
usePipeline,
+ usePipelineRun,
useSelectedNamespace
} from '../../api';
import { isValidLabel } from '../../utils';
@@ -56,7 +58,7 @@ const initialState = {
nodeSelector: [],
params: {},
paramSpecs: [],
- pendingPipelineStatus: '',
+ pipelinePendingStatus: '',
pipelineError: false,
pipelineRef: '',
pipelineRunName: '',
@@ -91,6 +93,11 @@ function CreatePipelineRun() {
return urlSearchParams.get('pipelineName') || '';
}
+ function getPipelineRunName() {
+ const urlSearchParams = new URLSearchParams(location.search);
+ return urlSearchParams.get('pipelineRunName') || '';
+ }
+
function getNamespace() {
const urlSearchParams = new URLSearchParams(location.search);
return (
@@ -426,6 +433,36 @@ function CreatePipelineRun() {
}
if (isYAMLMode()) {
+ const externalPipelineRunName = getPipelineRunName();
+ if (externalPipelineRunName) {
+ const { data: pipelineRunObject, isLoading } = usePipelineRun(
+ {
+ name: externalPipelineRunName,
+ namespace: getNamespace()
+ },
+ { disableWebSocket: true }
+ );
+ let payloadYaml = null;
+ if (pipelineRunObject) {
+ const { payload } = generateNewPipelineRunPayload(
+ pipelineRunObject,
+ false
+ );
+ payloadYaml = yaml.dump(payload);
+ }
+ const loadingMessage = intl.formatMessage({
+ id: 'dashboard.pipelineRun.loading',
+ defaultMessage: 'Loading PipelineRun…'
+ });
+
+ return (
+
+ );
+ }
const pipelineRun = getPipelineRunPayload({
labels: labels.reduce((acc, { key, value }) => {
acc[key] = value;
diff --git a/src/containers/CreatePipelineRun/CreatePipelineRun.test.js b/src/containers/CreatePipelineRun/CreatePipelineRun.test.js
index 272da4a9a4..5d95a327a8 100644
--- a/src/containers/CreatePipelineRun/CreatePipelineRun.test.js
+++ b/src/containers/CreatePipelineRun/CreatePipelineRun.test.js
@@ -12,7 +12,7 @@ limitations under the License.
*/
import React from 'react';
-import { fireEvent } from '@testing-library/react';
+import { fireEvent, waitFor } from '@testing-library/react';
import { renderWithRouter } from '../../utils/test';
@@ -23,6 +23,7 @@ import * as PipelineRunsAPI from '../../api/pipelineRuns';
import * as PipelinesAPI from '../../api/pipelines';
import * as ServiceAccountsAPI from '../../api/serviceAccounts';
+const submitButton = allByText => allByText('Create')[0];
const pipelines = [
{
metadata: {
@@ -110,14 +111,6 @@ describe('CreatePipelineRun', () => {
expect(queryByDisplayValue(/bar/i)).toBeFalsy();
});
- it('renders yaml mode', () => {
- const { getByRole } = renderWithRouter(, {
- path: '/create',
- route: '/create?mode=yaml'
- });
- expect(getByRole(/textbox/)).toBeTruthy();
- });
-
it('handles onClose event', () => {
jest.spyOn(window.history, 'pushState');
const { getByText } = renderWithRouter();
@@ -133,3 +126,101 @@ describe('CreatePipelineRun', () => {
expect(window.history.pushState).toHaveBeenCalledTimes(2);
});
});
+
+const pipelineRunRawGenerateName = {
+ apiVersion: 'tekton.dev/v1beta1',
+ kind: 'PipelineRun',
+ metadata: {
+ generateName: 'test-pipeline-run-name-',
+ namespace: 'test-namespace'
+ },
+ spec: {
+ pipelineSpec: {
+ tasks: [
+ {
+ name: 'hello',
+ taskSpec: {
+ steps: [
+ {
+ image: 'busybox',
+ name: 'echo',
+ script: '#!/bin/ash\necho "Hello World!"\n'
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+};
+
+const expectedPipelineRun = `apiVersion: tekton.dev/v1beta1
+kind: PipelineRun
+metadata:
+ name: run-1111111111
+ namespace: test-namespace
+ labels: {}
+spec:
+ pipelineRef:
+ name: ''
+ status: ''`;
+const expectedPipelineRunOneLine = expectedPipelineRun.replace(/\r?\n|\r/g, '');
+const findNameRegexp = /name: run-\S+/;
+describe('CreatePipelineRun yaml mode', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ jest.spyOn(window.history, 'pushState');
+ });
+
+ it('renders yaml mode with namespace', async () => {
+ jest
+ .spyOn(PipelineRunsAPI, 'createPipelineRunRaw')
+ .mockImplementation(() => Promise.resolve({ data: {} }));
+ jest
+ .spyOn(PipelineRunsAPI, 'usePipelineRun')
+ .mockImplementation(() => ({ data: pipelineRunRawGenerateName }));
+
+ const { getByRole } = renderWithRouter(, {
+ path: '/create',
+ route: '/create?mode=yaml&namespace=test-namespace'
+ });
+
+ expect(getByRole(/textbox/)).toBeTruthy();
+ let actual = getByRole(/textbox/).textContent;
+ actual = actual.replace(findNameRegexp, 'name: run-1111111111');
+ expect(actual.trim()).toEqual(expectedPipelineRunOneLine);
+ });
+
+ it('handle submit. yaml mode with pipelinerun and namespace', async () => {
+ jest
+ .spyOn(PipelineRunsAPI, 'createPipelineRunRaw')
+ .mockImplementation(() => Promise.resolve({ data: {} }));
+ jest
+ .spyOn(PipelineRunsAPI, 'usePipelineRun')
+ .mockImplementation(() => ({ data: pipelineRunRawGenerateName }));
+
+ const { queryAllByText } = renderWithRouter(, {
+ path: '/create',
+ route:
+ '/create?mode=yaml&pipelineRunName=test-pipeline-run-name&namespace=test-namespace'
+ });
+
+ expect(submitButton(queryAllByText)).toBeTruthy();
+
+ fireEvent.click(submitButton(queryAllByText));
+
+ await waitFor(() => {
+ expect(PipelineRunsAPI.createPipelineRunRaw).toHaveBeenCalledTimes(1);
+ });
+ expect(PipelineRunsAPI.createPipelineRunRaw).toHaveBeenCalledWith(
+ expect.objectContaining({
+ namespace: 'test-namespace',
+ payload: pipelineRunRawGenerateName
+ })
+ );
+ await waitFor(() => {
+ expect(window.history.pushState).toHaveBeenCalledTimes(2);
+ });
+ });
+});
diff --git a/src/containers/CreatePipelineRun/YAMLEditor.js b/src/containers/CreatePipelineRun/YAMLEditor.js
index 88f481da43..19e2d4b877 100644
--- a/src/containers/CreatePipelineRun/YAMLEditor.js
+++ b/src/containers/CreatePipelineRun/YAMLEditor.js
@@ -16,29 +16,41 @@ import {
Button,
Form,
FormGroup,
- InlineNotification
+ InlineNotification,
+ Loading
} from 'carbon-components-react';
import yaml from 'js-yaml';
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import CodeMirror from '@uiw/react-codemirror';
import { StreamLanguage } from '@codemirror/language';
import { yaml as codeMirrorYAML } from '@codemirror/legacy-modes/mode/yaml';
import { useNavigate } from 'react-router-dom-v5-compat';
import { createPipelineRunRaw, useSelectedNamespace } from '../../api';
-export function CreateYAMLEditor({ code: initialCode = '' }) {
+export function CreateYAMLEditor({
+ code: initialCode,
+ loading = false,
+ loadingMessage = ''
+}) {
const intl = useIntl();
const navigate = useNavigate();
const { selectedNamespace } = useSelectedNamespace();
- const [{ code, isCreating, submitError, validationErrorMessage }, setState] =
+ const [{ isCreating, submitError, validationErrorMessage, code }, setState] =
useState({
- code: initialCode,
isCreating: false,
submitError: '',
- validationErrorMessage: ''
+ validationErrorMessage: '',
+ code: initialCode
});
+ useEffect(() => {
+ setState(state => ({
+ ...state,
+ code: initialCode
+ }));
+ }, [loading]);
+
function validateNamespace(obj) {
if (!obj?.metadata?.namespace) {
return {
@@ -176,14 +188,25 @@ export function CreateYAMLEditor({ code: initialCode = '' }) {
lowContrast
/>
)}
-
-
+
+ <>
+ {loading && (
+
+
+ {loadingMessage}
+
+ )}
+ {!loading && (
+
+ )}
+ >