Improve delete confirmation dialogs with copyable resource ID and warning (#2464)

* feat: add copyable ID to delete confirmation dialogs

When deleting databases or containers, the confirmation dialog now displays
the resource ID in a read-only text field with a copy button, allowing users
to copy-paste the ID into the confirmation input instead of typing it manually.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fixed formatting.

* revert non-en locale changes; add localization instruction

Revert changes to non-English locale files — translations are managed
by a separate localization process. Add a note to copilot instructions
clarifying that only en/Resources.json should be modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: capitalize 'Id' in copyable resource ID labels

Changed 'id:' to 'Id:' in the copyable ID labels for delete confirmation
dialogs (both database and collection).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: capitalize collection name in copyable ID label

Use getCollectionName() directly (returns 'Container', 'Collection', etc.)
instead of the lowercased collectionName variable for the copyable ID label.
The database panel already used getDatabaseName() which returns capitalized.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: add warning message to delete container confirmation dialog

Added the same warning banner that exists in the delete database dialog
to the delete container dialog, informing users that the action cannot
be undone and will permanently delete the resource and its children.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
jawelton74
2026-04-28 07:48:33 -07:00
committed by GitHub
parent 277022969c
commit 5bf0970b5e
9 changed files with 3460 additions and 24 deletions
+2
View File
@@ -109,6 +109,8 @@ const title = t("splashScreen.title.default");
```
The `ResourceKey` type (derived from `Resources.json`) ensures compile-time safety — invalid keys will cause a type error. When adding new strings, add the English entry to `Resources.json` first, then reference it with `t()`.
**Important:** Only modify the English resource file (`src/Localization/en/Resources.json`). Do not modify non-English locale files (`src/Localization/<locale>/Resources.json`) — translations are managed by a separate localization process.
### Imports
TypeScript `baseUrl` is set to `src/`, so imports from `src/` are written without a leading `./src/` prefix:
+1
View File
@@ -15904,6 +15904,7 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -112,6 +112,9 @@ describe("Delete Collection Confirmation Pane", () => {
const wrapper = mount(<DeleteCollectionConfirmationPane refreshDatabases={() => undefined} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#copyableCollectionId")).toBe(true);
expect(wrapper.find("#copyableCollectionId").hostNodes().prop("value")).toBe(selectedCollectionId);
expect(wrapper.exists("#confirmCollectionId")).toBe(true);
wrapper
.find("#confirmCollectionId")
@@ -1,4 +1,4 @@
import { Text, TextField } from "@fluentui/react";
import { IconButton, Text, TextField } from "@fluentui/react";
import { Areas } from "Common/Constants";
import DeleteFeedback from "Common/DeleteFeedback";
import { getErrorMessage, getErrorStack } from "Common/ErrorHandlingUtils";
@@ -17,6 +17,7 @@ import React, { FunctionComponent, useState } from "react";
import { useDatabases } from "../../useDatabases";
import { useSelectedNode } from "../../useSelectedNode";
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "../PanelInfoErrorComponent";
const themedTextFieldStyles = {
fieldGroup: {
@@ -54,6 +55,10 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
const collectionName = getCollectionName().toLocaleLowerCase();
const paneTitle = t(Keys.panes.deleteCollection.panelTitle, { collectionName });
const selectedCollection = useSelectedNode.getState().selectedNode
? useSelectedNode.getState().findSelectedCollection()
: undefined;
const selectedCollectionId = selectedCollection?.id() ?? "";
const onSubmit = async (): Promise<void> => {
const collection = useSelectedNode.getState().findSelectedCollection();
@@ -131,6 +136,14 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
submitButtonText: t(Keys.common.ok),
onSubmit,
};
const errorProps: PanelInfoErrorProps = {
messageType: "warning",
showErrorDetails: false,
message: t(Keys.panes.deleteCollection.warningMessage),
};
const copyableIdLabel = t(Keys.panes.deleteCollection.copyableId, {
collectionName: getCollectionName(),
});
const confirmContainer = t(Keys.panes.deleteCollection.confirmPrompt, {
collectionName: collectionName.toLowerCase(),
});
@@ -140,9 +153,29 @@ export const DeleteCollectionConfirmationPane: FunctionComponent<DeleteCollectio
t(Keys.panes.deleteCollection.feedbackReason, { collectionName });
return (
<RightPaneForm {...props}>
{!formError && <PanelInfoErrorComponent {...errorProps} />}
<div className="panelFormWrapper">
<div className="panelMainContent">
<div className="confirmDeleteInput">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{copyableIdLabel}
</Text>
<TextField
id="copyableCollectionId"
readOnly
value={selectedCollectionId}
styles={themedTextFieldStyles}
onRenderSuffix={() => (
<IconButton
iconProps={{ iconName: "Copy" }}
title={t(Keys.common.copy)}
ariaLabel={t(Keys.common.copy)}
onClick={() => navigator.clipboard.writeText(selectedCollectionId)}
styles={{ root: { height: "100%" } }}
/>
)}
ariaLabel={copyableIdLabel}
/>
<span className="mandatoryStar">* </span>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{confirmContainer}
@@ -62,13 +62,15 @@ describe("Delete Database Confirmation Pane", () => {
const wrapper = mount(<DeleteDatabaseConfirmationPanel refreshDatabases={() => undefined} />);
expect(wrapper).toMatchSnapshot();
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
expect(wrapper.exists("#copyableDatabaseId")).toBe(true);
expect(wrapper.find("#copyableDatabaseId").hostNodes().prop("value")).toBe(selectedDatabaseId);
wrapper
.find("#confirmDatabaseId")
.hostNodes()
.simulate("change", { target: { value: selectedDatabaseId } });
expect(wrapper.exists("button")).toBe(true);
wrapper.find("button").hostNodes().simulate("submit");
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
wrapper.unmount();
});
@@ -1,4 +1,4 @@
import { Text, TextField } from "@fluentui/react";
import { IconButton, Text, TextField } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import { Areas } from "Common/Constants";
import DeleteFeedback from "Common/DeleteFeedback";
@@ -150,6 +150,7 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
showErrorDetails: false,
message: t(Keys.panes.deleteDatabase.warningMessage),
};
const copyableIdLabel = t(Keys.panes.deleteDatabase.copyableId, { databaseName: getDatabaseName() });
const confirmDatabase = t(Keys.panes.deleteDatabase.confirmPrompt, { databaseName: getDatabaseName() });
const reasonInfo =
t(Keys.panes.deleteDatabase.feedbackTitle) +
@@ -160,6 +161,25 @@ export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseCo
{!formError && <PanelInfoErrorComponent {...errorProps} />}
<div className="panelMainContent">
<div className="confirmDeleteInput">
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{copyableIdLabel}
</Text>
<TextField
id="copyableDatabaseId"
readOnly
value={selectedDatabase?.id() ?? ""}
styles={themedTextFieldStyles}
onRenderSuffix={() => (
<IconButton
iconProps={{ iconName: "Copy" }}
title={t(Keys.common.copy)}
ariaLabel={t(Keys.common.copy)}
onClick={() => navigator.clipboard.writeText(selectedDatabase?.id() ?? "")}
styles={{ root: { height: "100%" } }}
/>
)}
ariaLabel={copyableIdLabel}
/>
<span className="mandatoryStar">* </span>
<Text variant="small" style={{ color: "var(--colorNeutralForeground1)" }}>
{confirmDatabase}
File diff suppressed because it is too large Load Diff
+3
View File
@@ -420,6 +420,7 @@
"deleteDatabase": {
"panelTitle": "Delete {{databaseName}}",
"warningMessage": "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
"copyableId": "{{databaseName}} Id:",
"confirmPrompt": "Confirm by typing the {{databaseName}} id (name)",
"inputMismatch": "Input {{databaseName}} name \"{{input}}\" does not match the selected {{databaseName}} \"{{selectedId}}\"",
"feedbackTitle": "Help us improve Azure Cosmos DB!",
@@ -427,6 +428,8 @@
},
"deleteCollection": {
"panelTitle": "Delete {{collectionName}}",
"warningMessage": "Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
"copyableId": "{{collectionName}} Id:",
"confirmPrompt": "Confirm by typing the {{collectionName}} id",
"inputMismatch": "Input id {{input}} does not match the selected {{selectedId}}",
"feedbackTitle": "Help us improve Azure Cosmos DB!",