diff --git a/package.json b/package.json
index 0f454bb0..c612350d 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"react-router-dom-v5-compat": "6.22.3",
"sort-package-json": "^2.10.0",
"style-loader": "^3.3.1",
+ "css-loader": "^6.7.1",
"stylelint": "^15.3.0",
"stylelint-config-prettier": "9.0.3",
"stylelint-config-standard": "^31.0.0",
diff --git a/src/brokers/add-broker/AddBroker.component.test.tsx b/src/brokers/add-broker/AddBroker.component.test.tsx
new file mode 100644
index 00000000..757e8e1a
--- /dev/null
+++ b/src/brokers/add-broker/AddBroker.component.test.tsx
@@ -0,0 +1,129 @@
+import { FC, useReducer } from 'react';
+import {
+ BrokerCreationFormDispatch,
+ BrokerCreationFormState,
+ artemisCrReducer,
+ newArtemisCRState,
+} from '../../reducers/7.12/reducer';
+import { fireEvent, render, screen } from '../../test-utils';
+import { AddBroker } from './AddBroker.component';
+import { useAccessReview } from '@openshift-console/dynamic-plugin-sdk';
+
+jest.mock('@openshift-console/dynamic-plugin-sdk', () => ({
+ useAccessReview: jest.fn(() => []),
+}));
+
+const mockUseAccessReview = useAccessReview as jest.Mock;
+
+const SimplifiedCreaterBrokerPage: FC = () => {
+ const onSubmit = () => {
+ return 0;
+ };
+ const onCancel = () => {
+ return 0;
+ };
+ const initialValues = newArtemisCRState('default');
+ const [brokerModel, dispatch] = useReducer(artemisCrReducer, initialValues);
+ return (
+
+
+
+
+
+ );
+};
+
+const SimplifiedUpdateBrokerPage: FC = () => {
+ const onSubmit = () => {
+ return 0;
+ };
+ const onCancel = () => {
+ return 0;
+ };
+ const reloadExisting = () => {
+ return 0;
+ };
+ const initialValues = newArtemisCRState('default');
+ const [brokerModel, dispatch] = useReducer(artemisCrReducer, initialValues);
+ return (
+
+
+
+
+
+ );
+};
+
+describe('create broker', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ mockUseAccessReview.mockReturnValue([true, false]);
+ });
+ it('clicking on cancel after making some changes displays a warning', async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /plus/i }));
+ fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+ expect(
+ screen.getByText(
+ "You are about to quit the editor, the broker won't get created",
+ ),
+ ).toBeInTheDocument();
+ });
+ it("clicking on cancel immediately after opening the editor shouldn't display a warning", async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+ expect(
+ screen.queryByText(
+ "You are about to quit the editor, the broker won't get created",
+ ),
+ ).not.toBeInTheDocument();
+ });
+});
+
+describe('update broker', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+
+ mockUseAccessReview.mockReturnValue([true, false]);
+ });
+ it('clicking on cancel after making some changes displays a warning', async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /plus/i }));
+ fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+ expect(
+ screen.queryByText(
+ 'You are about to quit the editor, configuration that is not applied will be lost',
+ ),
+ ).toBeInTheDocument();
+ });
+ it("clicking on cancel immediately after opening the editor shouldn't display a warning", async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
+ expect(
+ screen.queryByText(
+ 'You are about to quit the editor, configuration that is not applied will be lost',
+ ),
+ ).not.toBeInTheDocument();
+ });
+ it('clicking on reload after making some changes displays a warning', async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /plus/i }));
+ fireEvent.click(screen.getByRole('button', { name: /reload/i }));
+ expect(
+ screen.queryByText('Upon reloading, these modifications will be lost.'),
+ ).toBeInTheDocument();
+ });
+ it("clicking on reload immediately after opening the editor shouldn't display a warning", async () => {
+ render();
+ fireEvent.click(screen.getByRole('button', { name: /reload/i }));
+ expect(
+ screen.queryByText('Upon reloading, these modifications will be lost.'),
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/src/brokers/add-broker/AddBroker.component.tsx b/src/brokers/add-broker/AddBroker.component.tsx
index 321b95bb..bc1aa7b0 100644
--- a/src/brokers/add-broker/AddBroker.component.tsx
+++ b/src/brokers/add-broker/AddBroker.component.tsx
@@ -1,68 +1,212 @@
-import { FC, useContext } from 'react';
-import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
-import { AlertVariant, Divider } from '@patternfly/react-core';
+import { FC, useContext, useState } from 'react';
+import {
+ ActionGroup,
+ Alert,
+ AlertVariant,
+ Button,
+ ButtonVariant,
+ Divider,
+ Form,
+ FormFieldGroup,
+ Modal,
+ ModalVariant,
+} from '@patternfly/react-core';
import {
ArtemisReducerOperations,
BrokerCreationFormDispatch,
BrokerCreationFormState,
EditorType,
} from '../../reducers/7.12/reducer';
-import { useLocation } from 'react-router-dom-v5-compat';
import { FormView } from '../../shared-components/FormView/FormView';
-import { YamlEditorView } from '../../shared-components/YamlEditorView/YamlEditorView';
import { EditorToggle } from './components/EditorToggle/EditorToggle';
+import { Loading } from '../../shared-components/Loading/Loading';
+import { useAccessReview } from '@openshift-console/dynamic-plugin-sdk';
+import { AMQBrokerModel } from '../../k8s/models';
+import { useTranslation } from '../../i18n/i18n';
+import { YamlEditorView } from '../../shared-components/YamlEditorView/YamlEditorView';
-type AddBrokerProps = {
- onCreateBroker: (data?: K8sResourceCommon) => void;
- notification: {
- title: string;
- variant: AlertVariant;
- };
- isUpdate?: boolean;
+type AddBrokerPropTypes = {
+ onSubmit: () => void;
+ onCancel: () => void;
+ isUpdatingExisting?: boolean;
+ reloadExisting?: () => void;
};
-export const AddBroker: FC = ({
- onCreateBroker,
- notification,
- isUpdate,
+export const AddBroker: FC = ({
+ onSubmit,
+ onCancel: doQuit,
+ isUpdatingExisting,
+ reloadExisting,
}) => {
const formValues = useContext(BrokerCreationFormState);
const dispatch = useContext(BrokerCreationFormDispatch);
- const location = useLocation();
- const params = new URLSearchParams(location.search);
- const returnUrl = params.get('returnUrl') || '/k8s/all-namespaces/brokers';
const { editorType } = formValues;
+ const [userWantsToQuit, setUserWantsToQuit] = useState(false);
+ const [userWantsToReload, setUserWantsToReload] = useState(false);
+
+ const [pendingActionQuittingYAMLView, setPendingActionQuittingYAMLView] =
+ useState<'switch' | 'submit'>('switch');
+ const [wantsToQuitYamlView, setWantsToQuitYamlView] = useState(false);
const onSelectEditorType = (editorType: EditorType) => {
- dispatch({
- operation: ArtemisReducerOperations.setEditorType,
- payload: editorType,
- });
+ if (formValues.editorType === EditorType.YAML) {
+ if (editorType === EditorType.BROKER) {
+ setWantsToQuitYamlView(true);
+ }
+ setPendingActionQuittingYAMLView('switch');
+ } else {
+ dispatch({
+ operation: ArtemisReducerOperations.setEditorType,
+ payload: EditorType.YAML,
+ });
+ }
+ };
+ const [triggerDelayedSubmit, setTriggerDelayedSubmit] = useState(false);
+ const [prevTriggerDelayedSubmit, setPrevTriggerDelayedSubmit] =
+ useState(triggerDelayedSubmit);
+ const onQuittingYamlView = () => {
+ if (pendingActionQuittingYAMLView === 'switch') {
+ setWantsToQuitYamlView(false);
+ dispatch({
+ operation: ArtemisReducerOperations.setEditorType,
+ payload: EditorType.BROKER,
+ });
+ }
+ if (pendingActionQuittingYAMLView === 'submit') {
+ setWantsToQuitYamlView(false);
+ setTriggerDelayedSubmit(true);
+ }
};
+ if (triggerDelayedSubmit !== prevTriggerDelayedSubmit) {
+ if (triggerDelayedSubmit) {
+ setTriggerDelayedSubmit(false);
+ onSubmit();
+ }
+ setPrevTriggerDelayedSubmit(triggerDelayedSubmit);
+ }
+ const { t } = useTranslation();
+ const namespace = formValues.cr.metadata.namespace;
+ const [canCreateBroker, loadingAccessReview] = useAccessReview({
+ group: AMQBrokerModel.apiGroup,
+ resource: AMQBrokerModel.plural,
+ namespace,
+ verb: 'create',
+ });
+ if (loadingAccessReview) return ;
+ if (!canCreateBroker) {
+ return (
+
+ {t('you_do_not_have_write_access')}
+
+ );
+ }
return (
<>
+ setUserWantsToQuit(false)}
+ actions={[
+ ,
+ ,
+ ]}
+ >
+ You are about to quit the editor,{' '}
+ {isUpdatingExisting
+ ? 'configuration that is not applied will be lost'
+ : "the broker won't get created"}
+
+ setUserWantsToReload(false)}
+ actions={[
+ ,
+ ,
+ ]}
+ >
+ Upon reloading, these modifications will be lost.
+
- {editorType === EditorType.BROKER && (
-
- )}
+ {editorType === EditorType.BROKER && }
{editorType === EditorType.YAML && (
setWantsToQuitYamlView(false)}
/>
)}
+
>
);
};
diff --git a/src/brokers/add-broker/AddBroker.container.tsx b/src/brokers/add-broker/AddBroker.container.tsx
index 4b7883b5..7202d8d1 100644
--- a/src/brokers/add-broker/AddBroker.container.tsx
+++ b/src/brokers/add-broker/AddBroker.container.tsx
@@ -1,6 +1,6 @@
import { FC, useReducer, useState } from 'react';
import { k8sCreate } from '@openshift-console/dynamic-plugin-sdk';
-import { AlertVariant } from '@patternfly/react-core';
+import { Alert, AlertVariant } from '@patternfly/react-core';
import { AddBroker } from './AddBroker.component';
import { AMQBrokerModel } from '../../k8s/models';
import { BrokerCR } from '../../k8s/types';
@@ -23,26 +23,32 @@ export const AddBrokerPage: FC = () => {
const navigate = useNavigate();
const { ns: namespace } = useParams<{ ns?: string }>();
- const defaultNotification = { title: '', variant: AlertVariant.info };
-
const initialValues = newArtemisCRState(namespace);
//states
const [brokerModel, dispatch] = useReducer(artemisCrReducer, initialValues);
- const [notification, setNotification] = useState(defaultNotification);
+ const params = new URLSearchParams(location.search);
+ const returnUrl = params.get('returnUrl') || '/k8s/all-namespaces/brokers';
const handleRedirect = () => {
- navigate('/k8s/all-namespaces/brokers');
+ navigate(returnUrl);
};
+ const [hasBrokerUpdated, setHasBrokerUpdated] = useState(false);
+ const [alert, setAlert] = useState('');
const k8sCreateBroker = (content: BrokerCR) => {
k8sCreate({ model: AMQBrokerModel, data: content })
- .then(() => {
- setNotification(defaultNotification);
- handleRedirect();
- })
+ .then(
+ () => {
+ setAlert('');
+ setHasBrokerUpdated(true);
+ },
+ (reason: Error) => {
+ setAlert(reason.message);
+ },
+ )
.catch((e) => {
- setNotification({ title: e.message, variant: AlertVariant.danger });
+ setAlert(e.message);
});
};
@@ -60,18 +66,33 @@ export const AddBrokerPage: FC = () => {
if (!isLoading && !isDomainSet) {
dispatch({
operation: ArtemisReducerOperations.setIngressDomain,
- payload: clusterDomain,
+ payload: {
+ ingressUrl: clusterDomain,
+ isSetByUser: false,
+ },
});
setIsDomainSet(true);
}
+ if (hasBrokerUpdated && alert === '') {
+ handleRedirect();
+ }
+
return (
+ {alert !== '' && (
+
+ )}
k8sCreateBroker(brokerModel.cr)}
+ onCancel={handleRedirect}
/>
diff --git a/src/brokers/update-broker/UpdateBroker.container.tsx b/src/brokers/update-broker/UpdateBroker.container.tsx
index 7c09aea4..412bb773 100644
--- a/src/brokers/update-broker/UpdateBroker.container.tsx
+++ b/src/brokers/update-broker/UpdateBroker.container.tsx
@@ -1,6 +1,6 @@
import { FC, useState, useEffect, useReducer } from 'react';
import { k8sGet, k8sUpdate } from '@openshift-console/dynamic-plugin-sdk';
-import { AlertVariant } from '@patternfly/react-core';
+import { Alert, AlertVariant } from '@patternfly/react-core';
import { AddBroker } from '../add-broker/AddBroker.component';
import { Loading } from '../../shared-components/Loading/Loading';
import { AMQBrokerModel } from '../../k8s/models';
@@ -13,20 +13,26 @@ import {
artemisCrReducer,
getArtemisCRState,
} from '../../reducers/7.12/reducer';
-import { useParams } from 'react-router-dom-v5-compat';
+import { useNavigate, useParams } from 'react-router-dom-v5-compat';
export const UpdateBrokerPage: FC = () => {
+ const navigate = useNavigate();
const { ns: namespace, name } = useParams<{ ns?: string; name?: string }>();
- const defaultNotification = { title: '', variant: AlertVariant.info };
//states
- const [notification, setNotification] = useState(defaultNotification);
const [loadingBrokerCR, setLoading] = useState(false);
const crState = getArtemisCRState(name, namespace);
const [brokerModel, dispatch] = useReducer(artemisCrReducer, crState);
+ const [hasBrokerUpdated, setHasBrokerUpdated] = useState(false);
+ const [alert, setAlert] = useState('');
+ const params = new URLSearchParams(location.search);
+ const returnUrl = params.get('returnUrl') || '/k8s/all-namespaces/brokers';
+ const handleRedirect = () => {
+ navigate(returnUrl);
+ };
const k8sUpdateBroker = (content: BrokerCR) => {
k8sUpdate({
model: AMQBrokerModel,
@@ -34,16 +40,17 @@ export const UpdateBrokerPage: FC = () => {
ns: namespace,
name: name,
})
- .then((response: BrokerCR) => {
- const name = response.metadata.name;
- const resourceVersion = response.metadata.resourceVersion;
- setNotification({
- title: `${name} has been updated to version ${resourceVersion}`,
- variant: AlertVariant.success,
- });
- })
- .catch((e) => {
- setNotification({ title: e.message, variant: AlertVariant.danger });
+ .then(
+ () => {
+ setAlert('');
+ setHasBrokerUpdated(true);
+ },
+ (reason: Error) => {
+ setAlert(reason.message);
+ },
+ )
+ .catch((e: Error) => {
+ setAlert(e.message);
});
};
@@ -53,13 +60,11 @@ export const UpdateBrokerPage: FC = () => {
.then((broker: BrokerCR) => {
dispatch({
operation: ArtemisReducerOperations.setModel,
- payload: {
- model: broker,
- },
+ payload: { model: broker, isSetByUser: false },
});
})
.catch((e) => {
- setNotification({ title: e.message, variant: AlertVariant.danger });
+ setAlert(e.message);
})
.finally(() => {
setLoading(false);
@@ -76,24 +81,41 @@ export const UpdateBrokerPage: FC = () => {
if (!loadingBrokerCR && !isLoadingClusterDomain && !isDomainSet) {
dispatch({
operation: ArtemisReducerOperations.setIngressDomain,
- payload: clusterDomain,
+ payload: {
+ ingressUrl: clusterDomain,
+ isSetByUser: false,
+ },
});
setIsDomainSet(true);
}
- if (loadingBrokerCR && !notification.title) return ;
+ if (loadingBrokerCR && !alert) return ;
if (!brokerModel.cr.spec?.deploymentPlan) {
return ;
}
+ if (hasBrokerUpdated && alert === '') {
+ handleRedirect();
+ }
+
return (
+ {alert !== '' && (
+
+ )}
k8sUpdateBroker(brokerModel.cr)}
+ onCancel={handleRedirect}
+ isUpdatingExisting
+ reloadExisting={k8sGetBroker}
/>
diff --git a/src/reducers/7.12/import-types.ts b/src/reducers/7.12/import-types.ts
index 9a04df6a..0d064441 100644
--- a/src/reducers/7.12/import-types.ts
+++ b/src/reducers/7.12/import-types.ts
@@ -4,5 +4,7 @@ import { EditorType } from './reducer';
export interface AddBrokerResourceValues {
shouldShowYAMLMessage?: boolean;
editorType?: EditorType;
+ yamlHasUnsavedChanges?: boolean;
+ hasChanges?: boolean;
cr?: BrokerCR;
}
diff --git a/src/reducers/7.12/reducer.test.ts b/src/reducers/7.12/reducer.test.ts
index 75ec89c4..96d9e1b5 100644
--- a/src/reducers/7.12/reducer.test.ts
+++ b/src/reducers/7.12/reducer.test.ts
@@ -1104,4 +1104,40 @@ describe('test the creation broker reducer', () => {
});
expect(stateExposeModeIngress.cr.spec.acceptors[0].expose).toBe(true);
});
+ it('test setYamlHasUnsavedChanges,', () => {
+ const initialState = newArtemisCRState('namespace');
+ const updatedState = artemisCrReducer(initialState, {
+ operation: ArtemisReducerOperations.setYamlHasUnsavedChanges,
+ });
+ expect(updatedState.yamlHasUnsavedChanges).toBe(true);
+ expect(updatedState.hasChanges).toBe(false);
+ });
+ it('test machine controlled model update resets the changed flags,', () => {
+ const initialState = newArtemisCRState('namespace');
+ const updatedState = artemisCrReducer(initialState, {
+ operation: ArtemisReducerOperations.setModel,
+ payload: {
+ model: initialState.cr,
+ isSetByUser: false,
+ },
+ });
+ expect(updatedState.yamlHasUnsavedChanges).toBe(false);
+ expect(updatedState.hasChanges).toBe(false);
+ });
+ it('test user controlled model update updates the flags correctly', () => {
+ const initialState = newArtemisCRState('namespace');
+ let updatedState = artemisCrReducer(initialState, {
+ operation: ArtemisReducerOperations.setYamlHasUnsavedChanges,
+ });
+ expect(updatedState.hasChanges).toBe(false);
+ updatedState = artemisCrReducer(updatedState, {
+ operation: ArtemisReducerOperations.setModel,
+ payload: {
+ model: initialState.cr,
+ isSetByUser: true,
+ },
+ });
+ expect(updatedState.yamlHasUnsavedChanges).toBe(false);
+ expect(updatedState.hasChanges).toBe(true);
+ });
});
diff --git a/src/reducers/7.12/reducer.ts b/src/reducers/7.12/reducer.ts
index e9fa8d81..b296b058 100644
--- a/src/reducers/7.12/reducer.ts
+++ b/src/reducers/7.12/reducer.ts
@@ -36,7 +36,10 @@ export const getArtemisCRState = (name: string, ns: string): FormState => {
const key = name + ns;
let formState = artemisCRStateMap.get(key);
if (!formState) {
- formState = {};
+ formState = {
+ yamlHasUnsavedChanges: false,
+ hasChanges: false,
+ };
formState.shouldShowYAMLMessage = true;
formState.editorType = EditorType.BROKER;
artemisCRStateMap.set(key, formState);
@@ -73,6 +76,8 @@ export const newArtemisCRState = (namespace: string): FormState => {
shouldShowYAMLMessage: true,
editorType: EditorType.BROKER,
cr: initialCr,
+ hasChanges: false,
+ yamlHasUnsavedChanges: false,
};
};
@@ -178,6 +183,11 @@ export enum ArtemisReducerOperations {
setNamespace,
/** update the total number of replicas */
setReplicasNumber,
+ /**
+ * Tells that the yaml editor has unsaved changes, when the setModel is
+ * invoked, the flag is reset to false.
+ */
+ setYamlHasUnsavedChanges,
/** Updates the configuration's factory Class */
updateAcceptorFactoryClass,
/** Update the issuer of an annotation */
@@ -229,10 +239,14 @@ type ArtemisReducerActions =
| SetModelAction
| SetNamespaceAction
| SetReplicasNumberAction
+ | SetYamlHasUnsavedChanges
| UpdateAcceptorFactoryClassAction
| UpdateAnnotationIssuerAction
| UpdateConnectorFactoryClassAction;
+interface SetYamlHasUnsavedChanges extends ArtemisReducerActionBase {
+ operation: ArtemisReducerOperations.setYamlHasUnsavedChanges;
+}
interface UpdateAnnotationIssuerAction extends ArtemisReducerActionBase {
operation: ArtemisReducerOperations.updateAnnotationIssuer;
payload: {
@@ -406,6 +420,9 @@ interface SetModelAction extends ArtemisReducerActionBase {
operation: ArtemisReducerOperations.setModel;
payload: {
model: BrokerCR;
+ /** setting this to true means that form state will get considered as
+ * modified, setting to false reset that status.*/
+ isSetByUser?: boolean;
};
}
@@ -569,8 +586,16 @@ interface SetReplicasNumberAction extends ArtemisReducerActionBase {
interface SetIngressDomainAction extends ArtemisReducerActionBase {
operation: ArtemisReducerOperations.setIngressDomain;
- // the domain of the cluster
- payload: string;
+ /** the domain of the cluster. Only passing the string is equivalent as saying
+ * that the value is set by the user. Otherwise this value can be customized.
+ * Setting isSetByUser to false has for effect to state that the form doesn't
+ * have changes, since the change is done by the system instead of the user.*/
+ payload:
+ | string
+ | {
+ ingressUrl: string;
+ isSetByUser?: boolean;
+ };
}
/**
*
@@ -584,9 +609,18 @@ export const artemisCrReducer: React.Reducer<
ArtemisReducerActions
> = (prevFormState, action) => {
const formState = { ...prevFormState };
+ if (
+ action.operation !== ArtemisReducerOperations.setEditorType &&
+ action.operation !== ArtemisReducerOperations.setYamlHasUnsavedChanges
+ ) {
+ formState.hasChanges = true;
+ }
// set the individual fields
switch (action.operation) {
+ case ArtemisReducerOperations.setYamlHasUnsavedChanges:
+ formState.yamlHasUnsavedChanges = true;
+ break;
case ArtemisReducerOperations.updateAnnotationIssuer:
updateAnnotationIssuer(
formState.cr,
@@ -612,6 +646,9 @@ export const artemisCrReducer: React.Reducer<
break;
case ArtemisReducerOperations.setEditorType:
formState.editorType = action.payload;
+ if (formState.editorType === EditorType.BROKER) {
+ formState.yamlHasUnsavedChanges = false;
+ }
break;
case ArtemisReducerOperations.setNamespace:
updateNamespace(formState.cr, action.payload);
@@ -842,9 +879,16 @@ export const artemisCrReducer: React.Reducer<
break;
case ArtemisReducerOperations.setModel:
setModel(formState, action.payload.model);
+ formState.yamlHasUnsavedChanges = false;
+ formState.hasChanges = action.payload.isSetByUser;
break;
case ArtemisReducerOperations.setIngressDomain:
- updateIngressDomain(formState.cr, action.payload);
+ if (typeof action.payload === 'string') {
+ updateIngressDomain(formState.cr, action.payload);
+ } else {
+ updateIngressDomain(formState.cr, action.payload.ingressUrl);
+ formState.hasChanges = action.payload.isSetByUser;
+ }
break;
default:
throw Error('Unknown action: ' + action);
diff --git a/src/shared-components/FormView/FormView.tsx b/src/shared-components/FormView/FormView.tsx
index 2bb039c3..b7c22c66 100644
--- a/src/shared-components/FormView/FormView.tsx
+++ b/src/shared-components/FormView/FormView.tsx
@@ -1,12 +1,5 @@
-import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
import {
- ActionGroup,
- Alert,
- AlertGroup,
- AlertVariant,
Banner,
- Button,
- ButtonVariant,
Form,
FormFieldGroup,
FormGroup,
@@ -20,8 +13,7 @@ import {
TextInput,
InputGroupItem,
} from '@patternfly/react-core';
-import { FC, useContext, useEffect, useState } from 'react';
-import { useTranslation } from '../../i18n/i18n';
+import { FC, useContext, useState } from 'react';
import {
ArtemisReducerOperations,
BrokerCreationFormDispatch,
@@ -31,68 +23,13 @@ import {
BrokerProperties,
BrokerPropertiesList,
} from './BrokerProperties/BrokerProperties';
-import { useNavigate } from 'react-router-dom-v5-compat';
-
-type FormViewProps = {
- onCreateBroker: (formValues: K8sResourceCommon) => void;
- notification: {
- title: string;
- variant: AlertVariant;
- };
- isUpdate: boolean;
- returnUrl: string;
-};
-
-export const FormView: FC = ({
- onCreateBroker,
- notification: serverNotification,
- isUpdate,
- returnUrl,
-}) => {
- const { t } = useTranslation();
- const navigate = useNavigate();
- const defaultNotification = { title: '', variant: AlertVariant.custom };
-
- //states
- const [notification, setNotification] = useState(defaultNotification);
+export const FormView: FC = () => {
const formState = useContext(BrokerCreationFormState);
const { cr } = useContext(BrokerCreationFormState);
const targetNs = cr.metadata.namespace;
const dispatch = useContext(BrokerCreationFormDispatch);
- useEffect(() => {
- setNotification(serverNotification);
- }, [serverNotification]);
-
- const validateFormFields = (formValues: K8sResourceCommon) => {
- const name = formValues.metadata.name;
- const regex =
- /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/i;
- if (!regex.test(name)) {
- setNotification({
- title: t('form_view_validation_info'),
- variant: AlertVariant.danger,
- });
- return false;
- } else {
- setNotification({ title: 'ok', variant: AlertVariant.success });
- return true;
- }
- };
-
- const onSubmit = () => {
- const isValid = validateFormFields(formState.cr);
- if (isValid) {
- onCreateBroker(formState.cr);
- navigate(returnUrl);
- }
- };
-
- const onCancel = () => {
- navigate(returnUrl);
- };
-
const handleNameChange = (name: string) => {
dispatch({
operation: ArtemisReducerOperations.setBrokerName,
@@ -125,17 +62,6 @@ export const FormView: FC = ({
return (
<>
-
>
);
};
diff --git a/src/shared-components/YamlEditorView/YamlEditorView.css b/src/shared-components/YamlEditorView/YamlEditorView.css
new file mode 100644
index 00000000..467b0e5b
--- /dev/null
+++ b/src/shared-components/YamlEditorView/YamlEditorView.css
@@ -0,0 +1,7 @@
+#reload-object{
+ display: none;
+}
+
+#cancel{
+ display: none;
+}
diff --git a/src/shared-components/YamlEditorView/YamlEditorView.tsx b/src/shared-components/YamlEditorView/YamlEditorView.tsx
index 9bb50ca1..17bb458d 100644
--- a/src/shared-components/YamlEditorView/YamlEditorView.tsx
+++ b/src/shared-components/YamlEditorView/YamlEditorView.tsx
@@ -1,149 +1,194 @@
-import { FC, Suspense, useContext } from 'react';
+import { FC, Suspense, useContext, useState } from 'react';
import {
Alert,
- AlertVariant,
+ AlertActionCloseButton,
AlertGroup,
- Page,
- ActionGroup,
+ AlertProps,
+ AlertVariant,
Button,
+ Hint,
+ HintBody,
+ Modal,
+ ModalVariant,
+ Page,
+ useInterval,
} from '@patternfly/react-core';
-import {
- CodeEditor,
- useAccessReview,
-} from '@openshift-console/dynamic-plugin-sdk';
-import { AMQBrokerModel } from '../../k8s/models';
-import { BrokerCR } from '../../k8s/types';
+import { ResourceYAMLEditor } from '@openshift-console/dynamic-plugin-sdk';
import { Loading } from '../../shared-components/Loading/Loading';
-import { useTranslation } from '../../i18n/i18n';
import {
ArtemisReducerOperations,
BrokerCreationFormDispatch,
BrokerCreationFormState,
} from '../../reducers/7.12/reducer';
-import YAML from 'yaml';
-import { useNavigate } from 'react-router-dom-v5-compat';
+import YAML, { YAMLParseError } from 'yaml';
+import './YamlEditorView.css';
-export type YamlEditorViewProps = {
- onCreateBroker: (content: any) => void;
- initialResourceYAML: BrokerCR;
- notification: {
- title: string;
- variant: AlertVariant;
- };
- isUpdate: boolean;
- returnUrl: string;
+type YamlEditorViewPropTypes = {
+ isAskingPermissionToClose: boolean;
+ permissionGranted: () => void;
+ permissionDenied: () => void;
};
-
-interface BrokerActionGroupProps {
- isUpdate: boolean;
- onSubmit: () => void;
- onCancel: () => void;
-}
-
-const BrokerActionGroup: FC = ({
- isUpdate,
- onSubmit,
- onCancel,
+export const YamlEditorView: FC = ({
+ isAskingPermissionToClose,
+ permissionGranted: permissionGranted,
+ permissionDenied,
}) => {
- const { t } = useTranslation();
+ const formState = useContext(BrokerCreationFormState);
+ const dispatch = useContext(BrokerCreationFormDispatch);
- return (
-
-
-
-
- );
-};
+ const [isModalVisible, setIsModalVisible] = useState(false);
-const YamlEditorView: FC = ({
- onCreateBroker,
- notification,
- isUpdate,
- returnUrl,
-}) => {
- const { t } = useTranslation();
- const navigate = useNavigate();
+ const [prevIsAskingPermissionToClose, setPrevIsAskingPermissionToClose] =
+ useState(isAskingPermissionToClose);
+ if (isAskingPermissionToClose !== prevIsAskingPermissionToClose) {
+ if (isAskingPermissionToClose) {
+ if (formState.yamlHasUnsavedChanges) {
+ setIsModalVisible(true);
+ } else {
+ permissionGranted();
+ }
+ }
+ setPrevIsAskingPermissionToClose(isAskingPermissionToClose);
+ }
- const fromState = useContext(BrokerCreationFormState);
- const namespace = fromState.cr.metadata.namespace;
- const dispatch = useContext(BrokerCreationFormDispatch);
+ const [currentYaml, setCurrentYaml] = useState();
+ const [yamlParseError, setYamlParserError] = useState();
- const [canCreateBroker, loadingAccessReview] = useAccessReview({
- group: AMQBrokerModel.apiGroup,
- resource: AMQBrokerModel.plural,
- namespace,
- verb: 'create',
- });
+ const getUniqueId = () => new Date().getTime();
- const onSave = () => {
- const yamlData: BrokerCR = fromState.cr;
- onCreateBroker(yamlData);
- navigate(returnUrl);
+ const updateModel = (content: string) => {
+ try {
+ dispatch({
+ operation: ArtemisReducerOperations.setModel,
+ payload: { model: YAML.parse(content), isSetByUser: true },
+ });
+ setYamlParserError(undefined);
+ addAlert('changes saved', 'success', getUniqueId());
+ return true;
+ } catch (e) {
+ setYamlParserError(e as YAMLParseError);
+ return false;
+ }
};
- const onCancel = () => {
- navigate(returnUrl);
- };
+ const stringedFormState = YAML.stringify(formState.cr, null, ' ');
+ const [alerts, setAlerts] = useState[]>([]);
- //event: contains information of changes
- //value: contains full yaml model
- const onChanges = (newValue: any, _event: any) => {
- dispatch({
- operation: ArtemisReducerOperations.setModel,
- payload: { model: YAML.parse(newValue) },
- });
+ const addAlert = (
+ title: string,
+ variant: AlertProps['variant'],
+ key: React.Key,
+ ) => {
+ setAlerts((prevAlerts) => [...prevAlerts, { title, variant, key }]);
};
- if (loadingAccessReview) return ;
-
+ const removeAlert = (key: React.Key) => {
+ const newAlerts = alerts.filter((alert) => alert.key !== key);
+ setAlerts(newAlerts);
+ };
+ const removeLastAlert = () => {
+ const newAlerts = alerts.filter(
+ (_alert, i, alerts) => i !== alerts.length - 1,
+ );
+ setAlerts(newAlerts);
+ };
+ useInterval(removeLastAlert, alerts.length > 0 ? 2000 : null);
return (
<>
- {canCreateBroker ? (
-
- {notification.title && (
-
-
-
- )}
- }>
-
-
-
-
- ) : (
-
+ {yamlParseError !== undefined && (
+
+ )}
+ setIsModalVisible(false)}
+ actions={[
+ ,
+ ,
+ ,
+ ]}
>
- {t('you_do_not_have_write_access')}
-
- )}
+ The YAML editor contains pending modifications, manual saving is
+ required.
+
+ {formState.yamlHasUnsavedChanges && (
+
+
+ Any changes in the YAML view has to be manually saved to get taken
+ into consideration.
+
+
+ )}
+
+ {alerts.map(({ key, variant, title }) => (
+ removeAlert(key)}
+ />
+ }
+ key={key}
+ />
+ ))}
+
+ }>
+ {
+ setCurrentYaml(newContent);
+ if (stringedFormState !== newContent) {
+ dispatch({
+ operation: ArtemisReducerOperations.setYamlHasUnsavedChanges,
+ });
+ }
+ }}
+ />
+
+
>
);
};
-export { YamlEditorView };
diff --git a/yarn.lock b/yarn.lock
index bcef0973..d2c4c9f3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3854,6 +3854,20 @@ css-functions-list@^3.2.1:
resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.2.tgz#9a54c6dd8416ed25c1079cd88234e927526c1922"
integrity sha512-c+N0v6wbKVxTu5gOBBFkr9BEdBWaqqjQeiJ8QvSRIJOf+UxlJh930m8e6/WNeODIK0mYLFkoONrnj16i2EcvfQ==
+css-loader@^6.7.1:
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba"
+ integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==
+ dependencies:
+ icss-utils "^5.1.0"
+ postcss "^8.4.33"
+ postcss-modules-extract-imports "^3.1.0"
+ postcss-modules-local-by-default "^4.0.5"
+ postcss-modules-scope "^3.2.0"
+ postcss-modules-values "^4.0.0"
+ postcss-value-parser "^4.2.0"
+ semver "^7.5.4"
+
css-select@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
@@ -5926,6 +5940,11 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
+icss-utils@^5.0.0, icss-utils@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
+ integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
+
ignore@^3.3.3, ignore@^3.3.5:
version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
@@ -8509,6 +8528,34 @@ postcss-media-query-parser@^0.2.3:
resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244"
integrity sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==
+postcss-modules-extract-imports@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002"
+ integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==
+
+postcss-modules-local-by-default@^4.0.5:
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f"
+ integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==
+ dependencies:
+ icss-utils "^5.0.0"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.1.0"
+
+postcss-modules-scope@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5"
+ integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==
+ dependencies:
+ postcss-selector-parser "^6.0.4"
+
+postcss-modules-values@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
+ integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
+ dependencies:
+ icss-utils "^5.0.0"
+
postcss-reporter@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-5.0.0.tgz#a14177fd1342829d291653f2786efd67110332c3"
@@ -8568,12 +8615,20 @@ postcss-selector-parser@^6.0.13:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
+postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
+ version "6.1.2"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
-postcss-value-parser@^4.2.0:
+postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@@ -8606,6 +8661,15 @@ postcss@^8.4.28:
picocolors "^1.0.1"
source-map-js "^1.2.0"
+postcss@^8.4.33:
+ version "8.4.45"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603"
+ integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==
+ dependencies:
+ nanoid "^3.3.7"
+ picocolors "^1.0.1"
+ source-map-js "^1.2.0"
+
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -9582,6 +9646,11 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.3, semver@^7.6.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13"
integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==
+semver@^7.5.4:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"