Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions extensions/mssql/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -3276,6 +3276,35 @@
"Database Project": "Database Project",
"Target": "Target",
"Comparison Details": "Comparison Details",
"Platform: {0}/{0} is the DacFx SqlServerVersion short name (e.g. 'SqlDwUnified', 'Sql160', 'SqlAzure') that the comparison ran under. Shown as a badge under each endpoint after the comparison runs.": {
"message": "Platform: {0}",
"comment": [
"{0} is the DacFx SqlServerVersion short name (e.g. 'SqlDwUnified', 'Sql160', 'SqlAzure') that the comparison ran under. Shown as a badge under each endpoint after the comparison runs."
]
},
"{0} platform: {1}/{0} is 'Source' or 'Target'{1} is the DacFx SqlServerVersion short name": {
"message": "{0} platform: {1}",
"comment": ["{0} is 'Source' or 'Target'", "{1} is the DacFx SqlServerVersion short name"]
},
"Affected child objects": "Affected child objects",
"Constraints added: {0}/{0} is a comma-separated list of fully-qualified object names (e.g. '[dbo].[PK_Customers], [dbo].[UQ_Customers_Email]') that will be added under the selected parent object when the diff is applied.": {
"message": "Constraints added: {0}",
"comment": [
"{0} is a comma-separated list of fully-qualified object names (e.g. '[dbo].[PK_Customers], [dbo].[UQ_Customers_Email]') that will be added under the selected parent object when the diff is applied."
]
},
"Constraints changed: {0}/{0} is a comma-separated list of fully-qualified object names that will be altered under the selected parent object when the diff is applied.": {
"message": "Constraints changed: {0}",
"comment": [
"{0} is a comma-separated list of fully-qualified object names that will be altered under the selected parent object when the diff is applied."
]
},
"Constraints dropped: {0}/{0} is a comma-separated list of fully-qualified object names that will be dropped from under the selected parent object when the diff is applied.": {
"message": "Constraints dropped: {0}",
"comment": [
"{0} is a comma-separated list of fully-qualified object names that will be dropped from under the selected parent object when the diff is applied."
]
},
"There was an error updating the project": "There was an error updating the project",
"Processing include or exclude all differences operation.": "Processing include or exclude all differences operation.",
"Publish Project - {0}/{0} is the name of the project being published": {
Expand Down
42 changes: 42 additions & 0 deletions extensions/mssql/src/webviews/common/locConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,48 @@ export class LocConstants {
source: l10n.t("Source"),
target: l10n.t("Target"),
compareDetails: l10n.t("Comparison Details"),
platformBadge: (platform: string) =>
l10n.t({
message: "Platform: {0}",
args: [platform],
comment: [
"{0} is the DacFx SqlServerVersion short name (e.g. 'SqlDwUnified', 'Sql160', 'SqlAzure') that the comparison ran under. Shown as a badge under each endpoint after the comparison runs.",
],
}),
platformBadgeAriaLabel: (endpointLabel: string, platform: string) =>
l10n.t({
message: "{0} platform: {1}",
args: [endpointLabel, platform],
comment: [
"{0} is 'Source' or 'Target'",
"{1} is the DacFx SqlServerVersion short name",
],
}),
affectedChildrenRegionLabel: l10n.t("Affected child objects"),
affectedChildrenAdded: (names: string) =>
l10n.t({
message: "Constraints added: {0}",
args: [names],
comment: [
"{0} is a comma-separated list of fully-qualified object names (e.g. '[dbo].[PK_Customers], [dbo].[UQ_Customers_Email]') that will be added under the selected parent object when the diff is applied.",
],
}),
affectedChildrenChanged: (names: string) =>
l10n.t({
message: "Constraints changed: {0}",
args: [names],
comment: [
"{0} is a comma-separated list of fully-qualified object names that will be altered under the selected parent object when the diff is applied.",
],
}),
affectedChildrenDropped: (names: string) =>
l10n.t({
message: "Constraints dropped: {0}",
args: [names],
comment: [
"{0} is a comma-separated list of fully-qualified object names that will be dropped from under the selected parent object when the diff is applied.",
],
}),
areYouSureYouWantToUpdateTheTarget: l10n.t(
"Are you sure you want to update the target?",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import { useVscodeWebview } from "../../../common/vscodeWebviewProvider";
import {
SchemaCompareReducers,
SchemaCompareWebViewState,
SchemaUpdateAction,
} from "../../../../sharedInterfaces/schemaCompare";
import { Divider, makeStyles, tokens } from "@fluentui/react-components";
import { Divider, makeStyles, Text, tokens } from "@fluentui/react-components";
import { locConstants as loc } from "../../../common/locConstants";
import { VscodeDiffEditor } from "../../../common/vscodeMonaco";
import * as mssql from "vscode-mssql";
import { getAggregatedScript, groupConstraintChildrenByAction } from "./compareDiffEditorUtils";
import "./compareDiffEditor.css";

const useStyles = makeStyles({
Expand All @@ -35,35 +36,21 @@ const useStyles = makeStyles({
display: "flex",
flexDirection: "column",
},
affectedChildrenContainer: {
// Subtle banner above the diff editor that lists the names of the diff's
// hierarchical-child changes (constraints under a table, columns under a view, etc.)
// so the user can see what other objects this diff will touch when applied.
padding: "4px 12px",
backgroundColor: tokens.colorNeutralBackground2,
borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
},
affectedChildrenLine: {
display: "block",
fontSize: "12px",
lineHeight: "1.5",
},
});

const getAggregatedScript = (diff: mssql.DiffEntry, getSourceScript: boolean): string => {
let script = "";
if (diff !== null) {
let diffScript = getSourceScript
? formatScript(diff.sourceScript)
: formatScript(diff.targetScript);
if (diffScript) {
script += diffScript + "\n\n";
}

diff.children.forEach((child) => {
let childScript = getAggregatedScript(child, getSourceScript);
script += childScript;
});
}

return script;
};

const formatScript = (script: string): string => {
if (!script) {
return "";
}

return script;
};

interface Props {
selectedDiffId: number;
renderSideBySide?: boolean;
Expand Down Expand Up @@ -102,13 +89,46 @@ const CompareDiffEditor = forwardRef<HTMLDivElement, Props>(
};
}, []);

const affectedChildrenByAction = groupConstraintChildrenByAction(diff);
const hasAffectedChildren = (Object.values(affectedChildrenByAction) as string[][]).some(
(names) => names && names.length > 0,
);

return (
<div ref={ref} className={classes.editorContainer}>
<div className={classes.dividerContainer}>
<Divider className={classes.dividerFont} alignContent="start">
{loc.schemaCompare.compareDetails}
</Divider>
</div>
{hasAffectedChildren && (
<div
className={classes.affectedChildrenContainer}
role="region"
aria-label={loc.schemaCompare.affectedChildrenRegionLabel}>
{affectedChildrenByAction[SchemaUpdateAction.Add]?.length ? (
<Text className={classes.affectedChildrenLine}>
{loc.schemaCompare.affectedChildrenAdded(
affectedChildrenByAction[SchemaUpdateAction.Add]!.join(", "),
)}
</Text>
) : null}
{affectedChildrenByAction[SchemaUpdateAction.Change]?.length ? (
<Text className={classes.affectedChildrenLine}>
{loc.schemaCompare.affectedChildrenChanged(
affectedChildrenByAction[SchemaUpdateAction.Change]!.join(", "),
)}
</Text>
) : null}
{affectedChildrenByAction[SchemaUpdateAction.Delete]?.length ? (
<Text className={classes.affectedChildrenLine}>
{loc.schemaCompare.affectedChildrenDropped(
affectedChildrenByAction[SchemaUpdateAction.Delete]!.join(", "),
)}
</Text>
) : null}
</div>
)}
<VscodeDiffEditor
height="100%"
language="sql"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@

import * as mssql from "vscode-mssql";
import { useContext } from "react";
import { Button, makeStyles, mergeClasses, shorthands, useId } from "@fluentui/react-components";
import {
Badge,
Button,
makeStyles,
mergeClasses,
shorthands,
useId,
} from "@fluentui/react-components";
import SelectSchemaInput from "./SelectSchemaInput";
import { schemaCompareContext } from "../SchemaCompareStateProvider";
import { useSchemaCompareSelector } from "../schemaCompareSelector";
Expand All @@ -25,6 +32,11 @@ const useStyles = makeStyles({
flexDirection: "row",
},

layoutVertically: {
display: "flex",
flexDirection: "column",
},

center: {
justifyContent: "center",
},
Expand All @@ -38,6 +50,14 @@ const useStyles = makeStyles({
buttonLeftMargin: {
marginLeft: "32px",
},

platformBadgeRow: {
// Reserve a row of vertical space even when no badge is shown so that the
// Compare button stays vertically aligned with the inputs both before and
// after the first comparison.
minHeight: "20px",
marginTop: "4px",
},
});

function getEndpointDisplayName(endpoint: mssql.SchemaCompareEndpointInfo): string {
Expand Down Expand Up @@ -68,6 +88,10 @@ const SelectSchemasPanel = ({ onSelectSchemaClicked }: Props) => {
);
const isComparisonInProgress = useSchemaCompareSelector((s) => s.isComparisonInProgress);
const isApplyInProgress = useSchemaCompareSelector((s) => s.isApplyInProgress);
// The DacFx platforms are only populated on schemaCompareResult after a comparison runs;
// pull them via a targeted selector so the panel does not re-render on every state change.
const sourcePlatform = useSchemaCompareSelector((s) => s.schemaCompareResult?.sourcePlatform);
const targetPlatform = useSchemaCompareSelector((s) => s.schemaCompareResult?.targetPlatform);

let sourceEndpointDisplay = getEndpointDisplayName(sourceEndpointInfo);
let targetEndpointDisplay = getEndpointDisplayName(targetEndpointInfo);
Expand All @@ -90,27 +114,45 @@ const SelectSchemasPanel = ({ onSelectSchemaClicked }: Props) => {
return true;
};

const renderPlatformBadge = (platform: string | undefined, endpointLabel: string) => (
<div className={classes.platformBadgeRow}>
{platform ? (
<Badge
appearance="outline"
size="small"
aria-label={loc.schemaCompare.platformBadgeAriaLabel(endpointLabel, platform)}>
{loc.schemaCompare.platformBadge(platform)}
</Badge>
) : null}
</div>
);

return (
<div
className={mergeClasses(classes.layoutHorizontally, classes.center, classes.topMargin)}>
<SelectSchemaInput
id={sourceId}
label={loc.schemaCompare.source}
buttonAriaLabel={loc.schemaCompare.selectSourceSchema}
value={sourceEndpointDisplay}
disableBrowseButton={isComparisonInProgress || isApplyInProgress}
selectFile={() => onSelectSchemaClicked("source")}
className={classes.marginRight}
/>

<SelectSchemaInput
id={targetId}
label={loc.schemaCompare.target}
buttonAriaLabel={loc.schemaCompare.selectTargetSchema}
value={targetEndpointDisplay}
disableBrowseButton={isComparisonInProgress || isApplyInProgress}
selectFile={() => onSelectSchemaClicked("target")}
/>
<div className={mergeClasses(classes.layoutVertically, classes.marginRight)}>
<SelectSchemaInput
id={sourceId}
label={loc.schemaCompare.source}
buttonAriaLabel={loc.schemaCompare.selectSourceSchema}
value={sourceEndpointDisplay}
disableBrowseButton={isComparisonInProgress || isApplyInProgress}
selectFile={() => onSelectSchemaClicked("source")}
/>
{renderPlatformBadge(sourcePlatform, loc.schemaCompare.source)}
</div>

<div className={classes.layoutVertically}>
<SelectSchemaInput
id={targetId}
label={loc.schemaCompare.target}
buttonAriaLabel={loc.schemaCompare.selectTargetSchema}
value={targetEndpointDisplay}
disableBrowseButton={isComparisonInProgress || isApplyInProgress}
selectFile={() => onSelectSchemaClicked("target")}
/>
{renderPlatformBadge(targetPlatform, loc.schemaCompare.target)}
</div>

<Button
className={mergeClasses(classes.button, classes.buttonLeftMargin)}
Expand Down
Loading