Skip to content

Commit

Permalink
frontend: Accept given YAML/JSON in EditorDialog
Browse files Browse the repository at this point in the history
Signed-off-by: Evangelos Skopelitis <[email protected]>
  • Loading branch information
skoeva committed Oct 29, 2024
1 parent 944c25b commit ad3cf8e
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 26 deletions.
3 changes: 2 additions & 1 deletion frontend/src/components/common/Resource/CreateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default function CreateButton(props: CreateButtonProps) {
const dispatchCreateEvent = useEventCallback(HeadlampEventType.CREATE_RESOURCE);
const clusters = useClusterGroup();
const [targetCluster, setTargetCluster] = React.useState(clusters[0] || '');
const [currentItem, setCurrentItem] = React.useState({}); // eslint-disable-line no-unused-vars

// When the clusters in the group change, we want to reset the target cluster
// if it's not in the new list of clusters.
Expand Down Expand Up @@ -148,7 +149,7 @@ export default function CreateButton(props: CreateButtonProps) {
</Button>
)}
<EditorDialog
item={{}}
item={currentItem}
open={openDialog}
onClose={() => setOpenDialog(false)}
onSave={handleSave}
Expand Down
64 changes: 39 additions & 25 deletions frontend/src/components/common/Resource/EditorDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ import SimpleEditor from './SimpleEditor';
type KubeObjectIsh = Partial<KubeObjectInterface>;

export interface EditorDialogProps extends DialogProps {
/** The object to edit, or null to make the dialog be in "loading mode". Pass it an empty object if no contents are to be shown when the dialog is first open. */
item: KubeObjectIsh | null;
/** The object(s) to edit, or null to make the dialog be in "loading mode". Pass it an empty object if no contents are to be shown when the dialog is first open. */
item: KubeObjectIsh | object | object[] | string | null;
/** Called when the dialog is closed. */
onClose: () => void;
/** Called when the user clicks the save button. */
Expand Down Expand Up @@ -88,11 +88,14 @@ export default function EditorDialog(props: EditorDialogProps) {
const [lang, setLang] = React.useState(i18n.language);
const themeName = getThemeName();

const originalCodeRef = React.useRef({ code: '', format: item ? 'yaml' : '' });
const initialCode = typeof item === 'string' ? item : yaml.dump(item || {});
const originalCodeRef = React.useRef({ code: initialCode, format: item ? 'yaml' : '' });
const [code, setCode] = React.useState(originalCodeRef.current);
const codeRef = React.useRef(code);
const lastCodeCheckHandler = React.useRef(0);
const previousVersionRef = React.useRef(item?.metadata?.resourceVersion || '');
const previousVersionRef = React.useRef(
isKubeObjectIsh(item) ? item?.metadata?.resourceVersion || '' : ''
);
const [error, setError] = React.useState('');
const [docSpecs, setDocSpecs] = React.useState<
KubeObjectInterface | KubeObjectInterface[] | null
Expand All @@ -109,36 +112,45 @@ export default function EditorDialog(props: EditorDialogProps) {
setUseSimpleEditorState(data);
}

function isKubeObjectIsh(item: any): item is KubeObjectIsh {
return item && typeof item === 'object' && !Array.isArray(item) && 'metadata' in item;
}

// Update the code when the item changes, but only if the code hasn't been touched.
React.useEffect(() => {
if (!item || Object.keys(item || {}).length === 0) {
const defaultCode = '# Enter your YAML or JSON here';
originalCodeRef.current = { code: defaultCode, format: 'yaml' };
setCode({ code: defaultCode, format: 'yaml' });
return;
}

const originalCode = originalCodeRef.current.code;
const itemCode =
originalCodeRef.current.format === 'json' ? JSON.stringify(item) : yaml.dump(item);
if (itemCode !== originalCodeRef.current.code) {
originalCodeRef.current = { code: itemCode, format: originalCodeRef.current.format };
}
// Determine the format (YAML or JSON) and serialize to string
const format = looksLikeJson(originalCodeRef.current.code) ? 'json' : 'yaml';
const itemCode = format === 'json' ? JSON.stringify(item) : yaml.dump(item);

if (!item.metadata) {
return;
// Update the code if the item representation has changed
if (itemCode !== originalCodeRef.current.code) {
originalCodeRef.current = { code: itemCode, format };
setCode({ code: itemCode, format });
}

const resourceVersionsDiffer =
(previousVersionRef.current || '') !== (item.metadata!.resourceVersion || '');
// Only change if the code hasn't been touched.
// We use the codeRef in this effect instead of the code, because we need to access the current
// state of the code but we don't want to trigger a re-render when we set the code here.
if (resourceVersionsDiffer || codeRef.current.code === originalCode) {
// Prevent updating to the same code, which would lead to an infinite loop.
if (codeRef.current.code !== itemCode) {
setCode({ code: itemCode, format: originalCodeRef.current.format });
}
// Additional handling for Kubernetes objects
if (isKubeObjectIsh(item) && item.metadata) {
const resourceVersionsDiffer =
(previousVersionRef.current || '') !== (item.metadata!.resourceVersion || '');
// Only change if the code hasn't been touched.
// We use the codeRef in this effect instead of the code, because we need to access the current
// state of the code but we don't want to trigger a re-render when we set the code here.
if (resourceVersionsDiffer || codeRef.current.code === originalCodeRef.current.code) {
// Prevent updating to the same code, which would lead to an infinite loop.
if (codeRef.current.code !== itemCode) {
setCode({ code: itemCode, format: originalCodeRef.current.format });
}

if (resourceVersionsDiffer && !!item.metadata!.resourceVersion) {
previousVersionRef.current = item.metadata!.resourceVersion;
if (resourceVersionsDiffer && !!item.metadata!.resourceVersion) {
previousVersionRef.current = item.metadata!.resourceVersion;
}
}
}
}, [item]);
Expand Down Expand Up @@ -309,7 +321,9 @@ export default function EditorDialog(props: EditorDialogProps) {
const errorLabel = error || errorMessage;
let dialogTitle = title;
if (!dialogTitle && item) {
const itemName = item.metadata?.name || t('New Object');
const itemName = isKubeObjectIsh(item)
? item.metadata?.name || t('New Object')
: t('New Object');
dialogTitle = isReadOnly()
? t('translation|View: {{ itemName }}', { itemName })
: t('translation|Edit: {{ itemName }}', { itemName });
Expand Down

0 comments on commit ad3cf8e

Please sign in to comment.