Skip to content

Commit

Permalink
feat: show diff of individual package resources (#36)
Browse files Browse the repository at this point in the history
This change shows the exact changes that a resource contains from the previous package revision or the upstream package.
  • Loading branch information
ChristopherFry committed Jun 13, 2022
1 parent abde941 commit c8dbd29
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 36 deletions.
37 changes: 27 additions & 10 deletions plugins/cad/src/components/Controls/YamlViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,24 @@
* limitations under the License.
*/

import Editor, { loader } from '@monaco-editor/react';
import Editor, { DiffEditor, loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import React from 'react';

type YamlViewerProps = {
height?: string;
width?: string;
original?: string;
value: string;
showDiff?: boolean;
allowEdit?: boolean;
onUpdatedValue?: (newValue: string) => void;
};

export const YamlViewer = ({
height,
width,
original,
value,
showDiff,
allowEdit,
onUpdatedValue,
}: YamlViewerProps) => {
Expand All @@ -41,18 +43,33 @@ export const YamlViewer = ({
}
};

const sharedEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions =
{
minimap: { enabled: false },
readOnly: !allowEdit,
scrollBeyondLastLine: false,
};

if (showDiff) {
return (
<DiffEditor
language="yaml"
original={original ?? ''}
modified={value}
options={{
renderSideBySide: false,
...sharedEditorOptions,
}}
/>
);
}

return (
<Editor
height={height}
width={width}
language="yaml"
value={value}
onChange={handleUpdatedValue}
options={{
minimap: { enabled: false },
readOnly: !allowEdit,
scrollBeyondLastLine: false,
}}
options={sharedEditorOptions}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { Table, TableColumn } from '@backstage/core-components';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { Button, Divider, IconButton, Menu, MenuItem } from '@material-ui/core';
import AddIcon from '@material-ui/icons/Add';
import DeleteIcon from '@material-ui/icons/Delete';
Expand Down Expand Up @@ -45,6 +46,7 @@ export enum ResourcesTableMode {

enum Dialog {
VIEWER = 'viewer',
DIFF_VIEWER = 'diff-viewer',
EDITOR = 'editor',
NONE = 'none',
}
Expand All @@ -59,6 +61,8 @@ type PackageRevisionResourcesTableProps = {
type ResourceRow = PackageResource & {
diffSummary: string;
isDeleted: boolean;
originalResource?: PackageResource;
currentResource?: PackageResource;
};

type KubernetesGKV = {
Expand All @@ -83,6 +87,7 @@ export const PackageRevisionResourcesTable = ({
}: PackageRevisionResourcesTableProps) => {
const [openDialog, setOpenDialog] = useState<Dialog>(Dialog.NONE);
const selectedDialogResource = useRef<DialogResource>();
const selectedDialogOriginalResource = useRef<DialogResource>();

const [addResourceAnchorEl, setAddResourceAnchorEl] =
React.useState<null | HTMLElement>(null);
Expand Down Expand Up @@ -126,13 +131,17 @@ export const PackageRevisionResourcesTable = ({
},
].sort((gvk1, gvk2) => (gvk1.kind > gvk2.kind ? 1 : -1));

const errorApi = useApi(errorApiRef);
const isEditMode = mode === ResourcesTableMode.EDIT;

const openResourceDialog = (
dialog: Dialog,
resource: DialogResource,
resource?: DialogResource,
originalResource?: DialogResource,
): void => {
selectedDialogResource.current = resource;
selectedDialogOriginalResource.current = originalResource;

setOpenDialog(dialog);
};

Expand All @@ -153,7 +162,7 @@ export const PackageRevisionResourcesTable = ({
onUpdatedResourcesMap(latestResourcesMap);
};

const rowOptions = (resourceRow: ResourceRow): JSX.Element[] => {
const renderRowOptions = (resourceRow: ResourceRow): JSX.Element[] => {
const options: JSX.Element[] = [];

if (isEditMode && !resourceRow.isDeleted) {
Expand Down Expand Up @@ -181,22 +190,53 @@ export const PackageRevisionResourcesTable = ({
return options;
};

const renderDiffColumn = (row: ResourceRow): JSX.Element | null => {
if (row.diffSummary) {
return (
<Button
variant="outlined"
style={{
position: 'absolute',
transform: 'translateY(-50%)',
}}
onClick={e => {
e.stopPropagation();
openResourceDialog(
Dialog.DIFF_VIEWER,
row.currentResource,
row.originalResource,
);
}}
>
{row.diffSummary}
</Button>
);
}

return null;
};

const columns: TableColumn<ResourceRow>[] = [
{ title: 'Kind', field: 'kind' },
{ title: 'Name', field: 'name' },
{ title: 'Namespace', field: 'namespace' },
{ title: '' },
{ title: '', render: resourceRow => <div>{rowOptions(resourceRow)}</div> },
{},
{ render: resourceRow => <div>{renderRowOptions(resourceRow)}</div> },
];

if (baseResourcesMap) {
columns[3] = { title: 'Diff', field: 'diffSummary' };
columns[3] = { title: 'Diff', render: renderDiffColumn };
}

const allResources = getPackageResourcesFromResourcesMap(
const packageResources = getPackageResourcesFromResourcesMap(
resourcesMap,
) as ResourceRow[];

const allResources: ResourceRow[] = packageResources.map(r => ({
...r,
currentResource: r,
}));

allResources.sort((resource1, resource2) => {
const resourceScore = (resource: ResourceRow): number => {
if (resource.kind === 'Kptfile') return 1000;
Expand Down Expand Up @@ -233,6 +273,7 @@ export const PackageRevisionResourcesTable = ({
diffSummary: 'Removed',
isDeleted: true,
yaml: '',
originalResource: resourceDiff.originalResource,
});
}

Expand All @@ -248,6 +289,8 @@ export const PackageRevisionResourcesTable = ({
);
}

thisResource.originalResource = resourceDiff.originalResource;

switch (resourceDiff.diffStatus) {
case ResourceDiffStatus.ADDED:
thisResource.diffSummary = 'Added';
Expand All @@ -258,7 +301,7 @@ export const PackageRevisionResourcesTable = ({
break;

case ResourceDiffStatus.UPDATED:
thisResource.diffSummary = `Updated (+${resourceDiff.linesAdded}, -${resourceDiff.linesRemoved})`;
thisResource.diffSummary = `Diff (+${resourceDiff.linesAdded}, -${resourceDiff.linesRemoved})`;
break;
case ResourceDiffStatus.UNCHANGED:
break;
Expand Down Expand Up @@ -406,18 +449,36 @@ export const PackageRevisionResourcesTable = ({
<ResourceViewerDialog
open={openDialog === Dialog.VIEWER}
onClose={closeDialog}
yaml={selectedDialogResource.current?.yaml ?? ''}
yaml={selectedDialogResource.current?.yaml}
originalYaml={selectedDialogOriginalResource.current?.yaml}
/>

<ResourceViewerDialog
open={openDialog === Dialog.DIFF_VIEWER}
onClose={closeDialog}
yaml={selectedDialogResource.current?.yaml}
originalYaml={selectedDialogOriginalResource.current?.yaml}
showDiff
/>

<Table<ResourceRow>
title="Resources"
options={{ search: false, paging: false }}
columns={columns}
data={allResources}
onRowClick={(_, resource) => {
if (resource) {
onRowClick={(_, row) => {
if (row) {
if (isEditMode && row.isDeleted) {
errorApi.post(new Error('Deleted resources cannot be updated.'));
return;
}

const dialog = isEditMode ? Dialog.EDITOR : Dialog.VIEWER;
openResourceDialog(dialog, resource);
openResourceDialog(
dialog,
row.currentResource,
row.originalResource,
);
}
}}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import { FirstClassViewerSelector } from './components/FirstClassViewerSelector'
type ResourceViewerProps = {
open: boolean;
onClose: () => void;
yaml: string;
yaml?: string;
originalYaml?: string;
showDiff?: boolean;
};

const useStyles = makeStyles({
Expand All @@ -48,6 +50,8 @@ export const ResourceViewerDialog = ({
open,
onClose,
yaml,
originalYaml,
showDiff,
}: ResourceViewerProps) => {
const [showYamlView, setShowYamlView] = useState<boolean>(false);
const classes = useStyles();
Expand All @@ -58,9 +62,12 @@ export const ResourceViewerDialog = ({
}
}, [open]);

if (!yaml) return <div />;
if (!yaml && !originalYaml) return <div />;

const resourceYaml = loadYaml(yaml) as KubernetesResource;
const isDeleted = !yaml;

const thisYaml = yaml || originalYaml || '';
const resourceYaml = loadYaml(thisYaml) as KubernetesResource;

const toggleView = (): void => {
setShowYamlView(!showYamlView);
Expand All @@ -69,12 +76,12 @@ export const ResourceViewerDialog = ({
const { kind, apiVersion } = resourceYaml;
const resourceName = resourceYaml.metadata.name;

const displayYamlHeight = (yaml.split('\n').length + 1) * 18;
const displayYamlHeight = (thisYaml.split('\n').length + 1) * 18;

return (
<Dialog open={open} onClose={onClose} maxWidth="lg">
<DialogTitle>
{kind} {resourceName}
{kind} {resourceName} {isDeleted && '(Removed)'}
</DialogTitle>
<DialogContent>
<Fragment>
Expand All @@ -83,18 +90,24 @@ export const ResourceViewerDialog = ({
style={{ height: `${displayYamlHeight}px` }}
>
{showYamlView ? (
<YamlViewer value={yaml} />
<YamlViewer
value={thisYaml}
original={originalYaml}
showDiff={showDiff}
/>
) : (
<FirstClassViewerSelector
apiVersion={apiVersion}
kind={kind}
yaml={yaml}
originalYaml={originalYaml}
showDiff={showDiff}
/>
)}
</div>

<Button variant="text" color="primary" onClick={toggleView}>
Show {showYamlView ? 'Formatted View' : 'YAML View'}{' '}
Show {showYamlView ? 'Formatted View' : 'YAML View'}
</Button>
</Fragment>
</DialogContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import {
type FirstClassViewerSelectorProps = {
apiVersion: string;
kind: string;
yaml: string;
yaml?: string;
originalYaml?: string;
showDiff?: boolean;
};

const getCustomMetadataFn = (
Expand Down Expand Up @@ -64,12 +66,19 @@ export const FirstClassViewerSelector = ({
apiVersion,
kind,
yaml,
originalYaml,
showDiff,
}: FirstClassViewerSelectorProps) => {
const groupVersionKind = `${apiVersion}/${kind}`;

const customMetadataFn = getCustomMetadataFn(groupVersionKind);

return (
<StructuredMetadata yaml={yaml} getCustomMetadata={customMetadataFn} />
<StructuredMetadata
yaml={yaml}
originalYaml={originalYaml}
getCustomMetadata={customMetadataFn}
showDiff={showDiff}
/>
);
};
Loading

0 comments on commit c8dbd29

Please sign in to comment.