Migration/delete database confirmation in react (#542)
Co-authored-by: Steve Faulkner <471400+southpolesteve@users.noreply.github.com> Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
This commit is contained in:
parent
6a69d3a77b
commit
c68e84a4b9
|
@ -1,23 +1,22 @@
|
||||||
import * as ko from "knockout";
|
|
||||||
import * as ViewModels from "../Contracts/ViewModels";
|
|
||||||
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
|
||||||
import AddCollectionIcon from "../../images/AddCollection.svg";
|
import AddCollectionIcon from "../../images/AddCollection.svg";
|
||||||
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
import AddSqlQueryIcon from "../../images/AddSqlQuery_16x16.svg";
|
||||||
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
|
||||||
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
import AddStoredProcedureIcon from "../../images/AddStoredProcedure.svg";
|
||||||
|
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
||||||
|
import AddUdfIcon from "../../images/AddUdf.svg";
|
||||||
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
import DeleteCollectionIcon from "../../images/DeleteCollection.svg";
|
||||||
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
import DeleteDatabaseIcon from "../../images/DeleteDatabase.svg";
|
||||||
import AddUdfIcon from "../../images/AddUdf.svg";
|
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
||||||
import AddTriggerIcon from "../../images/AddTrigger.svg";
|
|
||||||
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
|
||||||
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
|
||||||
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
|
import HostedTerminalIcon from "../../images/Hosted-Terminal.svg";
|
||||||
|
import * as ViewModels from "../Contracts/ViewModels";
|
||||||
|
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
||||||
|
import { userContext } from "../UserContext";
|
||||||
|
import { TreeNodeMenuItem } from "./Controls/TreeComponent/TreeComponent";
|
||||||
import Explorer from "./Explorer";
|
import Explorer from "./Explorer";
|
||||||
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
|
||||||
import StoredProcedure from "./Tree/StoredProcedure";
|
import StoredProcedure from "./Tree/StoredProcedure";
|
||||||
import Trigger from "./Tree/Trigger";
|
import Trigger from "./Tree/Trigger";
|
||||||
import { userContext } from "../UserContext";
|
import UserDefinedFunction from "./Tree/UserDefinedFunction";
|
||||||
import { DefaultAccountExperienceType } from "../DefaultAccountExperienceType";
|
|
||||||
|
|
||||||
export interface CollectionContextMenuButtonParams {
|
export interface CollectionContextMenuButtonParams {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
|
@ -43,7 +42,7 @@ export class ResourceTreeContextMenuButtonFactory {
|
||||||
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) {
|
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) {
|
||||||
items.push({
|
items.push({
|
||||||
iconSrc: DeleteDatabaseIcon,
|
iconSrc: DeleteDatabaseIcon,
|
||||||
onClick: () => container.deleteDatabaseConfirmationPane.open(),
|
onClick: () => container.openDeleteDatabaseConfirmationPane(),
|
||||||
label: container.deleteDatabaseText(),
|
label: container.deleteDatabaseText(),
|
||||||
styleClass: "deleteDatabaseMenuItem",
|
styleClass: "deleteDatabaseMenuItem",
|
||||||
});
|
});
|
||||||
|
|
|
@ -331,7 +331,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
|
||||||
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
|
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
|
||||||
placeholder={this.props.getTranslation(placeholderTKey)}
|
placeholder={this.props.getTranslation(placeholderTKey)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
dropdownWidth="auto"
|
// Removed dropdownWidth="auto" as dropdown accept only number
|
||||||
options={choices.map((c) => ({
|
options={choices.map((c) => ({
|
||||||
key: c.key,
|
key: c.key,
|
||||||
text: this.props.getTranslation(c.labelTKey),
|
text: this.props.getTranslation(c.labelTKey),
|
||||||
|
|
|
@ -285,7 +285,6 @@ exports[`SmartUiComponent disable all inputs 1`] = `
|
||||||
<StyledWithResponsiveMode
|
<StyledWithResponsiveMode
|
||||||
aria-labelledby="database-label"
|
aria-labelledby="database-label"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
dropdownWidth="auto"
|
|
||||||
id="database-dropdown-input"
|
id="database-dropdown-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
options={
|
||||||
|
@ -607,7 +606,6 @@ exports[`SmartUiComponent should render and honor input's hidden, disabled state
|
||||||
</StyledLabelBase>
|
</StyledLabelBase>
|
||||||
<StyledWithResponsiveMode
|
<StyledWithResponsiveMode
|
||||||
aria-labelledby="database-label"
|
aria-labelledby="database-label"
|
||||||
dropdownWidth="auto"
|
|
||||||
id="database-dropdown-input"
|
id="database-dropdown-input"
|
||||||
onChange={[Function]}
|
onChange={[Function]}
|
||||||
options={
|
options={
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
jest.mock("./../Common/dataAccess/deleteDatabase");
|
||||||
|
jest.mock("./../Shared/Telemetry/TelemetryProcessor");
|
||||||
|
import * as ko from "knockout";
|
||||||
|
import { deleteDatabase } from "./../Common/dataAccess/deleteDatabase";
|
||||||
|
import * as ViewModels from "./../Contracts/ViewModels";
|
||||||
|
import Explorer from "./Explorer";
|
||||||
|
|
||||||
|
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
|
||||||
|
let explorer: Explorer;
|
||||||
|
beforeAll(() => {
|
||||||
|
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
explorer = new Explorer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true if only 1 database", () => {
|
||||||
|
const database = {} as ViewModels.Database;
|
||||||
|
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||||
|
expect(explorer.isLastDatabase()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false if only 2 databases", () => {
|
||||||
|
const database = {} as ViewModels.Database;
|
||||||
|
const database2 = {} as ViewModels.Database;
|
||||||
|
explorer.databases = ko.observableArray<ViewModels.Database>([database, database2]);
|
||||||
|
expect(explorer.isLastDatabase()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false if not last empty database", () => {
|
||||||
|
const database = {} as ViewModels.Database;
|
||||||
|
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||||
|
expect(explorer.isLastNonEmptyDatabase()).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true if last non empty database", () => {
|
||||||
|
const database = {} as ViewModels.Database;
|
||||||
|
database.collections = ko.observableArray<ViewModels.Collection>([{} as ViewModels.Collection]);
|
||||||
|
explorer.databases = ko.observableArray<ViewModels.Database>([database]);
|
||||||
|
expect(explorer.isLastNonEmptyDatabase()).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -56,6 +56,7 @@ import { ContextualPaneBase } from "./Panes/ContextualPaneBase";
|
||||||
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
import DeleteCollectionConfirmationPane from "./Panes/DeleteCollectionConfirmationPane";
|
||||||
import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel";
|
import { DeleteCollectionConfirmationPanel } from "./Panes/DeleteCollectionConfirmationPanel";
|
||||||
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
|
import DeleteDatabaseConfirmationPane from "./Panes/DeleteDatabaseConfirmationPane";
|
||||||
|
import { DeleteDatabaseConfirmationPanel } from "./Panes/DeleteDatabaseConfirmationPanel";
|
||||||
import { ExecuteSprocParamsPanel } from "./Panes/ExecuteSprocParamsPanel";
|
import { ExecuteSprocParamsPanel } from "./Panes/ExecuteSprocParamsPanel";
|
||||||
import GraphStylingPane from "./Panes/GraphStylingPane";
|
import GraphStylingPane from "./Panes/GraphStylingPane";
|
||||||
import { LoadQueryPane } from "./Panes/LoadQueryPane";
|
import { LoadQueryPane } from "./Panes/LoadQueryPane";
|
||||||
|
@ -1299,7 +1300,12 @@ export default class Explorer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public isLastNonEmptyDatabase(): boolean {
|
public isLastNonEmptyDatabase(): boolean {
|
||||||
if (this.isLastDatabase() && this.databases()[0].collections && this.databases()[0].collections().length > 0) {
|
if (
|
||||||
|
this.isLastDatabase() &&
|
||||||
|
this.databases()[0] &&
|
||||||
|
this.databases()[0].collections &&
|
||||||
|
this.databases()[0].collections().length > 0
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -2432,6 +2438,18 @@ export default class Explorer {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openDeleteDatabaseConfirmationPane(): void {
|
||||||
|
this.openSidePanel(
|
||||||
|
"Delete Database",
|
||||||
|
<DeleteDatabaseConfirmationPanel
|
||||||
|
explorer={this}
|
||||||
|
openNotificationConsole={this.expandConsole}
|
||||||
|
closePanel={this.closeSidePanel}
|
||||||
|
selectedDatabase={this.findSelectedDatabase()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public openUploadItemsPanePane(): void {
|
public openUploadItemsPanePane(): void {
|
||||||
this.openSidePanel("Upload", <UploadItemsPane explorer={this} closePanel={this.closeSidePanel} />);
|
this.openSidePanel("Upload", <UploadItemsPane explorer={this} closePanel={this.closeSidePanel} />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
jest.mock("../../Common/dataAccess/deleteDatabase");
|
||||||
|
jest.mock("../../Shared/Telemetry/TelemetryProcessor");
|
||||||
|
import { mount, ReactWrapper, shallow } from "enzyme";
|
||||||
|
import * as ko from "knockout";
|
||||||
|
import React from "react";
|
||||||
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
|
import { ApiKind, DatabaseAccount } from "../../Contracts/DataModels";
|
||||||
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
|
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { updateUserContext } from "../../UserContext";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { DeleteDatabaseConfirmationPanel } from "./DeleteDatabaseConfirmationPanel";
|
||||||
|
|
||||||
|
describe("Delete Database Confirmation Pane", () => {
|
||||||
|
describe("shouldRecordFeedback()", () => {
|
||||||
|
it("should return true if last non empty database or is last database that has shared throughput, else false", () => {
|
||||||
|
const fakeExplorer = new Explorer();
|
||||||
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
|
fakeExplorer.isLastCollection = () => true;
|
||||||
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
|
||||||
|
const database = {} as Database;
|
||||||
|
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
||||||
|
database.id = ko.observable<string>("testDatabse");
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
explorer: fakeExplorer,
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
selectedDatabase: database,
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = shallow(<DeleteDatabaseConfirmationPanel {...props} />);
|
||||||
|
props.explorer.isLastNonEmptyDatabase = () => true;
|
||||||
|
wrapper.setProps(props);
|
||||||
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(true);
|
||||||
|
|
||||||
|
props.explorer.isLastNonEmptyDatabase = () => false;
|
||||||
|
props.explorer.isLastDatabase = () => false;
|
||||||
|
wrapper.setProps(props);
|
||||||
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
|
|
||||||
|
props.explorer.isLastNonEmptyDatabase = () => false;
|
||||||
|
props.explorer.isLastDatabase = () => true;
|
||||||
|
props.explorer.isSelectedDatabaseShared = () => false;
|
||||||
|
wrapper.setProps(props);
|
||||||
|
expect(wrapper.exists(".deleteDatabaseFeedback")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("submit()", () => {
|
||||||
|
const selectedDatabaseId = "testDatabse";
|
||||||
|
const fakeExplorer = new Explorer();
|
||||||
|
fakeExplorer.refreshAllDatabases = () => undefined;
|
||||||
|
fakeExplorer.isLastCollection = () => true;
|
||||||
|
fakeExplorer.isSelectedDatabaseShared = () => false;
|
||||||
|
|
||||||
|
let wrapper: ReactWrapper;
|
||||||
|
beforeAll(() => {
|
||||||
|
updateUserContext({
|
||||||
|
databaseAccount: {
|
||||||
|
name: "testDatabaseAccountName",
|
||||||
|
properties: {
|
||||||
|
cassandraEndpoint: "testEndpoint",
|
||||||
|
},
|
||||||
|
id: "testDatabaseAccountId",
|
||||||
|
} as DatabaseAccount,
|
||||||
|
defaultExperience: DefaultAccountExperienceType.DocumentDB,
|
||||||
|
});
|
||||||
|
(deleteDatabase as jest.Mock).mockResolvedValue(undefined);
|
||||||
|
(TelemetryProcessor.trace as jest.Mock).mockReturnValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const database = {} as Database;
|
||||||
|
database.collections = ko.observableArray<Collection>([{} as Collection]);
|
||||||
|
database.id = ko.observable<string>(selectedDatabaseId);
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
explorer: fakeExplorer,
|
||||||
|
closePanel: (): void => undefined,
|
||||||
|
openNotificationConsole: (): void => undefined,
|
||||||
|
selectedDatabase: database,
|
||||||
|
};
|
||||||
|
|
||||||
|
wrapper = mount(<DeleteDatabaseConfirmationPanel {...props} />);
|
||||||
|
props.explorer.isLastNonEmptyDatabase = () => true;
|
||||||
|
wrapper.setProps(props);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should call delete database", () => {
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find("#confirmDatabaseId")
|
||||||
|
.hostNodes()
|
||||||
|
.simulate("change", { target: { value: selectedDatabaseId } });
|
||||||
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
|
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should record feedback", async () => {
|
||||||
|
expect(wrapper.exists("#confirmDatabaseId")).toBe(true);
|
||||||
|
wrapper
|
||||||
|
.find("#confirmDatabaseId")
|
||||||
|
.hostNodes()
|
||||||
|
.simulate("change", { target: { value: selectedDatabaseId } });
|
||||||
|
|
||||||
|
expect(wrapper.exists("#deleteDatabaseFeedbackInput")).toBe(true);
|
||||||
|
const feedbackText = "Test delete Database feedback text";
|
||||||
|
wrapper
|
||||||
|
.find("#deleteDatabaseFeedbackInput")
|
||||||
|
.hostNodes()
|
||||||
|
.simulate("change", { target: { value: feedbackText } });
|
||||||
|
|
||||||
|
expect(wrapper.exists("#sidePanelOkButton")).toBe(true);
|
||||||
|
wrapper.find("#sidePanelOkButton").hostNodes().simulate("submit");
|
||||||
|
expect(deleteDatabase).toHaveBeenCalledWith(selectedDatabaseId);
|
||||||
|
|
||||||
|
const deleteFeedback = new DeleteFeedback(
|
||||||
|
"testDatabaseAccountId",
|
||||||
|
"testDatabaseAccountName",
|
||||||
|
ApiKind.SQL,
|
||||||
|
feedbackText
|
||||||
|
);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
expect(TelemetryProcessor.trace).toHaveBeenCalledWith(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||||
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback)),
|
||||||
|
});
|
||||||
|
wrapper.unmount();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,168 @@
|
||||||
|
import { useBoolean } from "@uifabric/react-hooks";
|
||||||
|
import { Text, TextField } from "office-ui-fabric-react";
|
||||||
|
import React, { FunctionComponent, useState } from "react";
|
||||||
|
import { Areas } from "../../Common/Constants";
|
||||||
|
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
|
||||||
|
import DeleteFeedback from "../../Common/DeleteFeedback";
|
||||||
|
import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
|
||||||
|
import { Collection, Database } from "../../Contracts/ViewModels";
|
||||||
|
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
|
||||||
|
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
|
||||||
|
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
|
||||||
|
import { userContext } from "../../UserContext";
|
||||||
|
import { logConsoleError } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
import Explorer from "../Explorer";
|
||||||
|
import { PanelFooterComponent } from "./PanelFooterComponent";
|
||||||
|
import { PanelInfoErrorComponent, PanelInfoErrorProps } from "./PanelInfoErrorComponent";
|
||||||
|
import { PanelLoadingScreen } from "./PanelLoadingScreen";
|
||||||
|
|
||||||
|
interface DeleteDatabaseConfirmationPanelProps {
|
||||||
|
explorer: Explorer;
|
||||||
|
closePanel: () => void;
|
||||||
|
openNotificationConsole: () => void;
|
||||||
|
selectedDatabase: Database;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DeleteDatabaseConfirmationPanel: FunctionComponent<DeleteDatabaseConfirmationPanelProps> = (
|
||||||
|
props: DeleteDatabaseConfirmationPanelProps
|
||||||
|
): JSX.Element => {
|
||||||
|
const [isLoading, { setTrue: setLoadingTrue, setFalse: setLoadingFalse }] = useBoolean(false);
|
||||||
|
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [databaseInput, setDatabaseInput] = useState<string>("");
|
||||||
|
const [databaseFeedbackInput, setDatabaseFeedbackInput] = useState<string>("");
|
||||||
|
|
||||||
|
const getPanelErrorProps = (): PanelInfoErrorProps => {
|
||||||
|
if (formError) {
|
||||||
|
return {
|
||||||
|
messageType: "error",
|
||||||
|
message: formError,
|
||||||
|
showErrorDetails: true,
|
||||||
|
openNotificationConsole: props.openNotificationConsole,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messageType: "warning",
|
||||||
|
showErrorDetails: false,
|
||||||
|
message:
|
||||||
|
"Warning! The action you are about to take cannot be undone. Continuing will permanently delete this resource and all of its children resources.",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async (event: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
||||||
|
const { selectedDatabase, explorer } = props;
|
||||||
|
event.preventDefault();
|
||||||
|
if (selectedDatabase?.id() && databaseInput !== selectedDatabase.id()) {
|
||||||
|
setFormError("Input database name does not match the selected database");
|
||||||
|
logConsoleError(`Error while deleting collection ${selectedDatabase && selectedDatabase.id()}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFormError("");
|
||||||
|
setLoadingTrue();
|
||||||
|
|
||||||
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteDatabase, {
|
||||||
|
databaseId: selectedDatabase.id(),
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: "Delete Database",
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteDatabase(selectedDatabase.id());
|
||||||
|
props.closePanel();
|
||||||
|
explorer.refreshAllDatabases();
|
||||||
|
explorer.tabsManager.closeTabsByComparator((tab) => tab.node?.id() === selectedDatabase.id());
|
||||||
|
explorer.selectedNode(undefined);
|
||||||
|
selectedDatabase
|
||||||
|
.collections()
|
||||||
|
.forEach((collection: Collection) =>
|
||||||
|
explorer.tabsManager.closeTabsByComparator(
|
||||||
|
(tab) => tab.node?.id() === collection.id() && (tab.node as Collection).databaseId === collection.databaseId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
TelemetryProcessor.traceSuccess(
|
||||||
|
Action.DeleteDatabase,
|
||||||
|
{
|
||||||
|
databaseId: selectedDatabase.id(),
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: "Delete Database",
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldRecordFeedback()) {
|
||||||
|
const deleteFeedback = new DeleteFeedback(
|
||||||
|
userContext?.databaseAccount.id,
|
||||||
|
userContext?.databaseAccount.name,
|
||||||
|
DefaultExperienceUtility.getApiKindFromDefaultExperience(userContext.defaultExperience),
|
||||||
|
databaseFeedbackInput
|
||||||
|
);
|
||||||
|
|
||||||
|
TelemetryProcessor.trace(Action.DeleteDatabase, ActionModifiers.Mark, {
|
||||||
|
message: JSON.stringify(deleteFeedback, Object.getOwnPropertyNames(deleteFeedback)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setLoadingFalse();
|
||||||
|
setFormError(error);
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
TelemetryProcessor.traceFailure(
|
||||||
|
Action.DeleteDatabase,
|
||||||
|
{
|
||||||
|
databaseId: selectedDatabase.id(),
|
||||||
|
dataExplorerArea: Areas.ContextualPane,
|
||||||
|
paneTitle: "Delete Database",
|
||||||
|
error: errorMessage,
|
||||||
|
errorStack: getErrorStack(error),
|
||||||
|
},
|
||||||
|
startKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldRecordFeedback = (): boolean => {
|
||||||
|
const { explorer } = props;
|
||||||
|
return explorer.isLastNonEmptyDatabase() || (explorer.isLastDatabase() && explorer.isSelectedDatabaseShared());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="panelFormWrapper" onSubmit={submit}>
|
||||||
|
<PanelInfoErrorComponent {...getPanelErrorProps()} />
|
||||||
|
<div className="panelMainContent">
|
||||||
|
<div className="confirmDeleteInput">
|
||||||
|
<span className="mandatoryStar">* </span>
|
||||||
|
<Text variant="small">Confirm by typing the database id</Text>
|
||||||
|
<TextField
|
||||||
|
id="confirmDatabaseId"
|
||||||
|
autoFocus
|
||||||
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
|
onChange={(event, newInput?: string) => {
|
||||||
|
setDatabaseInput(newInput);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{shouldRecordFeedback() && (
|
||||||
|
<div className="deleteDatabaseFeedback">
|
||||||
|
<Text variant="small" block>
|
||||||
|
Help us improve Azure Cosmos DB!
|
||||||
|
</Text>
|
||||||
|
<Text variant="small" block>
|
||||||
|
What is the reason why you are deleting this database?
|
||||||
|
</Text>
|
||||||
|
<TextField
|
||||||
|
id="deleteDatabaseFeedbackInput"
|
||||||
|
styles={{ fieldGroup: { width: 300 } }}
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
onChange={(event, newInput?: string) => {
|
||||||
|
setDatabaseFeedbackInput(newInput);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<PanelFooterComponent buttonLabel="OK" />
|
||||||
|
{isLoading && <PanelLoadingScreen />}
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,9 +1,9 @@
|
||||||
import * as React from "react";
|
|
||||||
import { IconButton, PrimaryButton } from "office-ui-fabric-react/lib/Button";
|
|
||||||
import { KeyCodes } from "../../Common/Constants";
|
|
||||||
import { Subscription } from "knockout";
|
import { Subscription } from "knockout";
|
||||||
|
import { IconButton, PrimaryButton } from "office-ui-fabric-react/lib/Button";
|
||||||
|
import * as React from "react";
|
||||||
import ErrorRedIcon from "../../../images/error_red.svg";
|
import ErrorRedIcon from "../../../images/error_red.svg";
|
||||||
import LoadingIndicatorIcon from "../../../images/LoadingIndicator_3Squares.gif";
|
import LoadingIndicatorIcon from "../../../images/LoadingIndicator_3Squares.gif";
|
||||||
|
import { KeyCodes } from "../../Common/Constants";
|
||||||
import Explorer from "../Explorer";
|
import Explorer from "../Explorer";
|
||||||
|
|
||||||
export interface GenericRightPaneProps {
|
export interface GenericRightPaneProps {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -107,12 +107,12 @@ describe("Collection Add and Delete Cassandra spec", () => {
|
||||||
await dbElements[0].click();
|
await dbElements[0].click();
|
||||||
|
|
||||||
// confirm delete database
|
// confirm delete database
|
||||||
await frame.waitForSelector('input[data-test="confirmDatabaseId"]', { visible: true });
|
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||||
await frame.waitFor(RENDER_DELAY);
|
await frame.waitFor(RENDER_DELAY);
|
||||||
await frame.type('input[data-test="confirmDatabaseId"]', keyspaceId.trim());
|
await frame.type('input[id="confirmDatabaseId"]', keyspaceId.trim());
|
||||||
|
|
||||||
// click delete
|
// click delete
|
||||||
await frame.click('input[data-test="deleteDatabase"]');
|
await frame.click('button[id="sidePanelOkButton"]');
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
await frame.waitFor(LOADING_STATE_DELAY);
|
await frame.waitFor(LOADING_STATE_DELAY);
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
|
@ -123,12 +123,12 @@ describe("Collection Add and Delete Mongo spec", () => {
|
||||||
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||||
|
|
||||||
// confirm delete database
|
// confirm delete database
|
||||||
await frame.waitForSelector('input[data-test="confirmDatabaseId"]', { visible: true });
|
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||||
await frame.waitFor(RENDER_DELAY);
|
await frame.waitFor(RENDER_DELAY);
|
||||||
await frame.type('input[data-test="confirmDatabaseId"]', selectedDbId);
|
await frame.type('input[id="confirmDatabaseId"]', selectedDbId);
|
||||||
|
|
||||||
// click delete
|
// click delete
|
||||||
await frame.click('input[data-test="deleteDatabase"]');
|
await frame.click('button[id="sidePanelOkButton"]');
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
await frame.waitFor(LOADING_STATE_DELAY);
|
await frame.waitFor(LOADING_STATE_DELAY);
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
|
@ -120,12 +120,12 @@ describe("Collection Add and Delete SQL spec", () => {
|
||||||
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
await frame.click('span[class="treeComponentMenuItemLabel deleteDatabaseMenuItemLabel"]');
|
||||||
|
|
||||||
// confirm delete database
|
// confirm delete database
|
||||||
await frame.waitForSelector('input[data-test="confirmDatabaseId"]', { visible: true });
|
await frame.waitForSelector('input[id="confirmDatabaseId"]', { visible: true });
|
||||||
await frame.waitFor(RENDER_DELAY);
|
await frame.waitFor(RENDER_DELAY);
|
||||||
await frame.type('input[data-test="confirmDatabaseId"]', selectedDbId);
|
await frame.type('input[id="confirmDatabaseId"]', selectedDbId);
|
||||||
|
|
||||||
// click delete
|
// click delete
|
||||||
await frame.click('input[data-test="deleteDatabase"]');
|
await frame.click('button[id="sidePanelOkButton"]');
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
await frame.waitFor(LOADING_STATE_DELAY);
|
await frame.waitFor(LOADING_STATE_DELAY);
|
||||||
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
|
||||||
|
|
Loading…
Reference in New Issue