Skip to content

Commit 78e6e1f

Browse files
authored
feat: YAML view improvements (#303)
1 parent 1718180 commit 78e6e1f

File tree

10 files changed

+167
-56
lines changed

10 files changed

+167
-56
lines changed

public/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@
371371
"update": "Update"
372372
},
373373
"yaml": {
374-
"YAML": "File"
374+
"YAML": "File",
375+
"showOnlyImportant": "Show only important fields"
375376
},
376377
"createMCP": {
377378
"dialogTitle": "Create Managed Control Plane",

src/components/Graphs/Graph.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Legend, LegendItem } from './Legend';
1010
import { YamlViewDialog } from '../Yaml/YamlViewDialog';
1111
import YamlViewer from '../Yaml/YamlViewer';
1212
import { stringify } from 'yaml';
13-
import { removeManagedFieldsProperty } from '../../utils/removeManagedFieldsProperty';
13+
import { removeManagedFieldsAndFilterData, Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
1414
import { useTranslation } from 'react-i18next';
1515
import { useGraph } from './useGraph';
1616
import { ManagedResourceItem } from '../../lib/shared/types';
@@ -44,7 +44,12 @@ const Graph: React.FC = () => {
4444
const { nodes, edges, colorMap, loading, error } = useGraph(colorBy, handleYamlClick);
4545

4646
const yamlString = useMemo(
47-
() => (yamlResource ? stringify(removeManagedFieldsProperty(yamlResource)) : ''),
47+
() => (yamlResource ? stringify(removeManagedFieldsAndFilterData(yamlResource as unknown as Resource, true)) : ''),
48+
[yamlResource],
49+
);
50+
51+
const yamlStringToCopy = useMemo(
52+
() => (yamlResource ? stringify(removeManagedFieldsAndFilterData(yamlResource as unknown as Resource, false)) : ''),
4853
[yamlResource],
4954
);
5055

@@ -134,7 +139,9 @@ const Graph: React.FC = () => {
134139
<YamlViewDialog
135140
isOpen={yamlDialogOpen}
136141
setIsOpen={setYamlDialogOpen}
137-
dialogContent={<YamlViewer yamlString={yamlString} filename={yamlFilename} />}
142+
dialogContent={
143+
<YamlViewer yamlString={yamlString} yamlStringToCopy={yamlStringToCopy} filename={yamlFilename} />
144+
}
138145
/>
139146
</div>
140147
);

src/components/Yaml/YamlLoader.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { YamlViewButtonProps } from './YamlViewButtonWithLoader.tsx';
2-
import { FC } from 'react';
2+
import { FC, useMemo } from 'react';
33

44
import { stringify } from 'yaml';
55

@@ -9,24 +9,47 @@ import Loading from '../Shared/Loading.tsx';
99
import IllustratedError from '../Shared/IllustratedError.tsx';
1010
import YamlViewer from './YamlViewer.tsx';
1111
import { useApiResource } from '../../lib/api/useApiResource';
12-
import { removeManagedFieldsProperty, Resource } from '../../utils/removeManagedFieldsProperty.ts';
12+
import { removeManagedFieldsAndFilterData, Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
1313

14-
export const YamlLoader: FC<YamlViewButtonProps> = ({ workspaceName, resourceType, resourceName }) => {
14+
interface YamlLoaderProps extends YamlViewButtonProps {
15+
showOnlyImportantData?: boolean;
16+
setShowOnlyImportantData?: (showOnlyImportantData: boolean) => void;
17+
}
18+
19+
export const YamlLoader: FC<YamlLoaderProps> = ({
20+
workspaceName,
21+
resourceType,
22+
resourceName,
23+
showOnlyImportantData = false,
24+
setShowOnlyImportantData,
25+
}) => {
1526
const { isLoading, data, error } = useApiResource(
1627
ResourceObject(workspaceName ?? '', resourceType, resourceName),
1728
undefined,
1829
true,
1930
);
2031
const { t } = useTranslation();
32+
const yamlString = useMemo(() => {
33+
if (isLoading || error) return '';
34+
return stringify(removeManagedFieldsAndFilterData(data as Resource, showOnlyImportantData));
35+
}, [data, error, isLoading, showOnlyImportantData]);
36+
37+
const yamlStringToCopy = useMemo(() => {
38+
if (isLoading || error) return '';
39+
return stringify(removeManagedFieldsAndFilterData(data as Resource, false));
40+
}, [data, error, isLoading]);
2141
if (isLoading) return <Loading />;
2242
if (error) {
2343
return <IllustratedError details={t('common.cannotLoadData')} />;
2444
}
2545

2646
return (
2747
<YamlViewer
28-
yamlString={stringify(removeManagedFieldsProperty(data as Resource))}
48+
yamlString={yamlString}
49+
yamlStringToCopy={yamlStringToCopy}
2950
filename={`${workspaceName ? `${workspaceName}_` : ''}${resourceType}_${resourceName}`}
51+
setShowOnlyImportantData={setShowOnlyImportantData}
52+
showOnlyImportantData={showOnlyImportantData}
3053
/>
3154
);
3255
};

src/components/Yaml/YamlViewButton.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import styles from './YamlViewer.module.css';
44
import { useTranslation } from 'react-i18next';
55
import YamlViewer from './YamlViewer.tsx';
66
import { stringify } from 'yaml';
7-
import { removeManagedFieldsProperty, Resource } from '../../utils/removeManagedFieldsProperty.ts';
7+
import { removeManagedFieldsAndFilterData, Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
88
import { YamlIcon } from './YamlIcon.tsx';
99
import { YamlViewDialog } from './YamlViewDialog.tsx';
1010

@@ -13,12 +13,16 @@ export type YamlViewButtonProps = {
1313
};
1414

1515
export const YamlViewButton: FC<YamlViewButtonProps> = ({ resourceObject }) => {
16+
const [showOnlyImportantData, setShowOnlyImportantData] = useState(true);
1617
const [isOpen, setIsOpen] = useState(false);
1718
const { t } = useTranslation();
1819
const resource = resourceObject as Resource;
1920

2021
const yamlString = useMemo(() => {
21-
return stringify(removeManagedFieldsProperty(resource));
22+
return stringify(removeManagedFieldsAndFilterData(resource, showOnlyImportantData));
23+
}, [resource, showOnlyImportantData]);
24+
const yamlStringToCopy = useMemo(() => {
25+
return stringify(removeManagedFieldsAndFilterData(resource, false));
2226
}, [resource]);
2327
return (
2428
<span>
@@ -27,10 +31,15 @@ export const YamlViewButton: FC<YamlViewButtonProps> = ({ resourceObject }) => {
2731
setIsOpen={setIsOpen}
2832
dialogContent={
2933
<YamlViewer
34+
yamlStringToCopy={yamlStringToCopy}
3035
yamlString={yamlString}
3136
filename={`${resource?.kind ?? ''}${resource?.metadata?.name ? '_' : ''}${resource?.metadata?.name ?? ''}`}
37+
setShowOnlyImportantData={setShowOnlyImportantData}
38+
showOnlyImportantData={showOnlyImportantData}
3239
/>
3340
}
41+
setShowOnlyImportantData={setShowOnlyImportantData}
42+
showOnlyImportantData={showOnlyImportantData}
3443
/>
3544

3645
<Button

src/components/Yaml/YamlViewButtonWithLoader.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export type YamlViewButtonProps = {
1313
};
1414

1515
export const YamlViewButtonWithLoader: FC<YamlViewButtonProps> = ({ workspaceName, resourceType, resourceName }) => {
16+
const [showOnlyImportantData, setShowOnlyImportantData] = useState(true);
1617
const [isOpen, setIsOpen] = useState(false);
1718
const { t } = useTranslation();
1819
return (
@@ -21,8 +22,16 @@ export const YamlViewButtonWithLoader: FC<YamlViewButtonProps> = ({ workspaceNam
2122
isOpen={isOpen}
2223
setIsOpen={setIsOpen}
2324
dialogContent={
24-
<YamlLoader workspaceName={workspaceName} resourceName={resourceName} resourceType={resourceType} />
25+
<YamlLoader
26+
workspaceName={workspaceName}
27+
resourceName={resourceName}
28+
resourceType={resourceType}
29+
showOnlyImportantData={showOnlyImportantData}
30+
setShowOnlyImportantData={setShowOnlyImportantData}
31+
/>
2532
}
33+
setShowOnlyImportantData={setShowOnlyImportantData}
34+
showOnlyImportantData={showOnlyImportantData}
2635
/>
2736

2837
<Button

src/components/Yaml/YamlViewDialog.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Bar, Button, Dialog } from '@ui5/webcomponents-react';
1+
import { Bar, Button, CheckBox, Dialog } from '@ui5/webcomponents-react';
22

33
import { FC, ReactNode } from 'react';
44
import { useTranslation } from 'react-i18next';
@@ -7,19 +7,40 @@ export type YamlViewDialogProps = {
77
isOpen: boolean;
88
setIsOpen: (isOpen: boolean) => void;
99
dialogContent: ReactNode;
10+
showOnlyImportantData?: boolean;
11+
setShowOnlyImportantData?: (showOnlyImportantData: boolean) => void;
1012
};
1113

12-
export const YamlViewDialog: FC<YamlViewDialogProps> = ({ isOpen, setIsOpen, dialogContent }) => {
14+
export const YamlViewDialog: FC<YamlViewDialogProps> = ({
15+
isOpen,
16+
setIsOpen,
17+
dialogContent,
18+
showOnlyImportantData,
19+
setShowOnlyImportantData,
20+
}) => {
1321
const { t } = useTranslation();
22+
const handleShowOnlyImportantData = () => {
23+
setShowOnlyImportantData?.(!showOnlyImportantData);
24+
};
1425
return (
1526
<Dialog
1627
open={isOpen}
1728
stretch
29+
initialFocus={'closeButton'}
1830
footer={
1931
<Bar
32+
startContent={
33+
setShowOnlyImportantData && (
34+
<CheckBox
35+
text={t('yaml.showOnlyImportant')}
36+
checked={showOnlyImportantData}
37+
onChange={handleShowOnlyImportantData}
38+
/>
39+
)
40+
}
2041
design="Footer"
2142
endContent={
22-
<Button design="Emphasized" onClick={() => setIsOpen(false)}>
43+
<Button design="Emphasized" id={'closeButton'} onClick={() => setIsOpen(false)}>
2344
{t('common.close')}
2445
</Button>
2546
}

src/components/Yaml/YamlViewer.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@ import styles from './YamlViewer.module.css';
77
import { useCopyToClipboard } from '../../hooks/useCopyToClipboard.ts';
88
import { useTranslation } from 'react-i18next';
99
import { useTheme } from '../../hooks/useTheme.ts';
10-
type YamlViewerProps = { yamlString: string; filename: string };
11-
const YamlViewer: FC<YamlViewerProps> = ({ yamlString, filename }) => {
10+
type YamlViewerProps = {
11+
yamlString: string;
12+
yamlStringToCopy: string;
13+
filename: string;
14+
showOnlyImportantData?: boolean;
15+
setShowOnlyImportantData?: (showOnlyImportantData: boolean) => void;
16+
};
17+
18+
// Download button is hidden now due to stakeholder request
19+
const SHOW_DOWNLOAD_BUTTON = false;
20+
21+
const YamlViewer: FC<YamlViewerProps> = ({ yamlString, filename, yamlStringToCopy }) => {
1222
const { t } = useTranslation();
1323
const { isDarkTheme } = useTheme();
1424
const { copyToClipboard } = useCopyToClipboard();
@@ -27,12 +37,14 @@ const YamlViewer: FC<YamlViewerProps> = ({ yamlString, filename }) => {
2737
return (
2838
<div className={styles.container}>
2939
<FlexBox className={styles.buttons} direction="Row" justifyContent="End" alignItems="Baseline" gap={16}>
30-
<Button icon="copy" onClick={() => copyToClipboard(yamlString)}>
40+
<Button icon="copy" onClick={() => copyToClipboard(yamlStringToCopy)}>
3141
{t('buttons.copy')}
3242
</Button>
33-
<Button icon="download" onClick={downloadYaml}>
34-
{t('buttons.download')}
35-
</Button>
43+
{SHOW_DOWNLOAD_BUTTON && (
44+
<Button icon="download" onClick={downloadYaml}>
45+
{t('buttons.download')}
46+
</Button>
47+
)}
3648
</FlexBox>
3749
<SyntaxHighlighter
3850
language="yaml"

src/lib/api/types/shared/keyNames.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export const CHARGING_TARGET_LABEL: string = 'openmcp.cloud.sap/charging-target'
33
export const CHARGING_TARGET_TYPE_LABEL: string = 'openmcp.cloud.sap/charging-target-type';
44
export const PROJECT_NAME_LABEL: string = 'openmcp.cloud/mcp-project';
55
export const WORKSPACE_LABEL: string = 'openmcp.cloud/mcp-workspace';
6+
export const LAST_APPLIED_CONFIGURATION_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration';
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { LAST_APPLIED_CONFIGURATION_ANNOTATION } from '../lib/api/types/shared/keyNames';
2+
3+
export type Resource = {
4+
apiVersion: string;
5+
kind: string;
6+
items?: Omit<Resource, 'items'>[];
7+
metadata: {
8+
name: string;
9+
namespace?: string;
10+
labels?: Record<string, string>;
11+
annotations?: {
12+
[LAST_APPLIED_CONFIGURATION_ANNOTATION]?: string;
13+
[key: string]: string | undefined;
14+
};
15+
managedFields?: unknown;
16+
creationTimestamp?: string;
17+
finalizers?: string[];
18+
generation?: number;
19+
resourceVersion?: string;
20+
uid?: string;
21+
};
22+
spec?: unknown;
23+
status?: unknown;
24+
};
25+
26+
const cleanUpResource = (
27+
resource: Omit<Resource, 'items'>,
28+
showOnlyImportantData: boolean,
29+
): Omit<Resource, 'items'> => {
30+
const newResource = { ...resource };
31+
32+
if (newResource.metadata) {
33+
newResource.metadata = { ...newResource.metadata };
34+
delete newResource.metadata.managedFields;
35+
36+
if (showOnlyImportantData) {
37+
if (newResource.metadata.annotations) {
38+
newResource.metadata.annotations = { ...newResource.metadata.annotations };
39+
delete newResource.metadata.annotations[LAST_APPLIED_CONFIGURATION_ANNOTATION];
40+
}
41+
delete newResource.metadata.generation;
42+
delete newResource.metadata.uid;
43+
}
44+
}
45+
46+
return newResource;
47+
};
48+
49+
export const removeManagedFieldsAndFilterData = (
50+
resourceObject: Resource,
51+
showOnlyImportantData: boolean,
52+
): Resource => {
53+
if (!resourceObject) {
54+
return {} as Resource;
55+
}
56+
if (resourceObject.items) {
57+
return {
58+
...cleanUpResource(resourceObject, showOnlyImportantData),
59+
items: resourceObject.items.map((item) => cleanUpResource(item, showOnlyImportantData)),
60+
};
61+
}
62+
63+
return cleanUpResource(resourceObject, showOnlyImportantData);
64+
};

src/utils/removeManagedFieldsProperty.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)