mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-11-27 16:07:01 +00:00
Fix AddTableEntityPanel (#945)
* Fix AddTableEntityPanel * Add CSS * Fix snapshot
This commit is contained in:
parent
8a3929775b
commit
afacde4041
@ -141,6 +141,7 @@ export default class NotebookManager {
|
|||||||
notebookContentRef={notebookContentRef}
|
notebookContentRef={notebookContentRef}
|
||||||
onTakeSnapshot={onTakeSnapshot}
|
onTakeSnapshot={onTakeSnapshot}
|
||||||
/>,
|
/>,
|
||||||
|
"440px",
|
||||||
onClosePanel
|
onClosePanel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,11 +85,12 @@ export class PanelContainerComponent extends React.Component<PanelContainerProps
|
|||||||
|
|
||||||
export const SidePanel: React.FC = () => {
|
export const SidePanel: React.FC = () => {
|
||||||
const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded);
|
const isConsoleExpanded = useNotificationConsole((state) => state.isExpanded);
|
||||||
const { isOpen, panelContent, headerText } = useSidePanel((state) => {
|
const { isOpen, panelContent, panelWidth, headerText } = useSidePanel((state) => {
|
||||||
return {
|
return {
|
||||||
isOpen: state.isOpen,
|
isOpen: state.isOpen,
|
||||||
panelContent: state.panelContent,
|
panelContent: state.panelContent,
|
||||||
headerText: state.headerText,
|
headerText: state.headerText,
|
||||||
|
panelWidth: state.panelWidth,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// TODO Refactor PanelContainerComponent into a functional component and remove this wrapper
|
// TODO Refactor PanelContainerComponent into a functional component and remove this wrapper
|
||||||
@ -100,6 +101,7 @@ export const SidePanel: React.FC = () => {
|
|||||||
panelContent={panelContent}
|
panelContent={panelContent}
|
||||||
headerText={headerText}
|
headerText={headerText}
|
||||||
isConsoleExpanded={isConsoleExpanded}
|
isConsoleExpanded={isConsoleExpanded}
|
||||||
|
panelWidth={panelWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { IDropdownOption, Image, IPanelProps, IRenderFunction, Label, Stack, Text, TextField } from "@fluentui/react";
|
import { IDropdownOption, Image, Label, Stack, Text, TextField } from "@fluentui/react";
|
||||||
import { useBoolean } from "@fluentui/react-hooks";
|
import { useBoolean } from "@fluentui/react-hooks";
|
||||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||||
import * as _ from "underscore";
|
import * as _ from "underscore";
|
||||||
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
import AddPropertyIcon from "../../../../images/Add-property.svg";
|
||||||
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
import RevertBackIcon from "../../../../images/RevertBack.svg";
|
||||||
|
import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
|
||||||
import { TableEntity } from "../../../Common/TableEntity";
|
import { TableEntity } from "../../../Common/TableEntity";
|
||||||
import { useSidePanel } from "../../../hooks/useSidePanel";
|
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
import * as TableConstants from "../../Tables/Constants";
|
import * as TableConstants from "../../Tables/Constants";
|
||||||
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
import * as DataTableUtilities from "../../Tables/DataTable/DataTableUtilities";
|
||||||
@ -15,7 +15,7 @@ import { CassandraAPIDataClient, CassandraTableKey, TableDataClient } from "../.
|
|||||||
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
import * as TableEntityProcessor from "../../Tables/TableEntityProcessor";
|
||||||
import * as Utilities from "../../Tables/Utilities";
|
import * as Utilities from "../../Tables/Utilities";
|
||||||
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
import QueryTablesTab from "../../Tabs/QueryTablesTab";
|
||||||
import { PanelContainerComponent } from "../PanelContainerComponent";
|
import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm";
|
||||||
import {
|
import {
|
||||||
attributeNameLabel,
|
attributeNameLabel,
|
||||||
attributeValueLabel,
|
attributeValueLabel,
|
||||||
@ -30,9 +30,7 @@ import {
|
|||||||
getCassandraDefaultEntities,
|
getCassandraDefaultEntities,
|
||||||
getDefaultEntities,
|
getDefaultEntities,
|
||||||
getEntityValuePlaceholder,
|
getEntityValuePlaceholder,
|
||||||
getPanelTitle,
|
|
||||||
imageProps,
|
imageProps,
|
||||||
isValidEntities,
|
|
||||||
options,
|
options,
|
||||||
} from "./Validators/EntityTableHelper";
|
} from "./Validators/EntityTableHelper";
|
||||||
|
|
||||||
@ -61,7 +59,6 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
tableEntityListViewModel,
|
tableEntityListViewModel,
|
||||||
cassandraApiClient,
|
cassandraApiClient,
|
||||||
}: AddTableEntityPanelProps): JSX.Element => {
|
}: AddTableEntityPanelProps): JSX.Element => {
|
||||||
const closeSidePanel = useSidePanel((state) => state.closeSidePanel);
|
|
||||||
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
const [entities, setEntities] = useState<EntityRowType[]>([]);
|
||||||
const [selectedRow, setSelectedRow] = useState<number>(0);
|
const [selectedRow, setSelectedRow] = useState<number>(0);
|
||||||
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
const [entityAttributeValue, setEntityAttributeValue] = useState<string>("");
|
||||||
@ -70,6 +67,8 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
isEntityValuePanelOpen,
|
isEntityValuePanelOpen,
|
||||||
{ setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse },
|
{ setTrue: setIsEntityValuePanelTrue, setFalse: setIsEntityValuePanelFalse },
|
||||||
] = useBoolean(false);
|
] = useBoolean(false);
|
||||||
|
const [formError, setFormError] = useState<string>("");
|
||||||
|
const [isExecuting, setIsExecuting] = useState<boolean>(false);
|
||||||
|
|
||||||
/* Get default and previous saved entity headers */
|
/* Get default and previous saved entity headers */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -98,19 +97,36 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/* Add new entity attribute */
|
/* Add new entity attribute */
|
||||||
const submit = async (event: React.FormEvent<HTMLInputElement>): Promise<void> => {
|
const onSubmit = async (): Promise<void> => {
|
||||||
if (!isValidEntities(entities)) {
|
for (let i = 0; i < entities.length; i++) {
|
||||||
return undefined;
|
const { property, type } = entities[i];
|
||||||
}
|
if (property === "" || property === undefined) {
|
||||||
event.preventDefault();
|
setFormError(`Property name cannot be empty. Please enter a property name`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
setFormError(`Property type cannot be empty. Please select a type from the dropdown for property ${property}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsExecuting(true);
|
||||||
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
const entity: Entities.ITableEntity = entityFromAttributes(entities);
|
||||||
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
const newEntity: Entities.ITableEntity = await tableDataClient.createDocument(queryTablesTab.collection, entity);
|
||||||
await tableEntityListViewModel.addEntityToCache(newEntity);
|
try {
|
||||||
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
await tableEntityListViewModel.addEntityToCache(newEntity);
|
||||||
tableEntityListViewModel.redrawTableThrottled();
|
if (!tryInsertNewHeaders(tableEntityListViewModel, newEntity)) {
|
||||||
|
tableEntityListViewModel.redrawTableThrottled();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
setFormError(errorMessage);
|
||||||
|
handleError(errorMessage, "AddTableRow");
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
setIsExecuting(false);
|
||||||
}
|
}
|
||||||
closeSidePanel();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
const tryInsertNewHeaders = (viewModel: TableEntityListViewModel, newEntity: Entities.ITableEntity): boolean => {
|
||||||
@ -200,110 +216,80 @@ export const AddTableEntityPanel: FunctionComponent<AddTableEntityPanelProps> =
|
|||||||
setIsEntityValuePanelTrue();
|
setIsEntityValuePanelTrue();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPanelContent = (): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<form className="panelFormWrapper">
|
|
||||||
<div className="panelFormWrapper">
|
|
||||||
<div className="panelMainContent">
|
|
||||||
{entities.map((entity, index) => {
|
|
||||||
return (
|
|
||||||
<TableEntity
|
|
||||||
key={"" + entity.id + index}
|
|
||||||
isDeleteOptionVisible={entity.isDeleteOptionVisible}
|
|
||||||
entityTypeLabel={index === 0 && dataTypeLabel}
|
|
||||||
entityPropertyLabel={index === 0 && attributeNameLabel}
|
|
||||||
entityValueLabel={index === 0 && attributeValueLabel}
|
|
||||||
options={userContext.apiType === "Cassandra" ? cassandraOptions : options}
|
|
||||||
isPropertyTypeDisable={entity.isPropertyTypeDisable}
|
|
||||||
entityProperty={entity.property}
|
|
||||||
selectedKey={entity.type}
|
|
||||||
entityPropertyPlaceHolder={detailedHelp}
|
|
||||||
entityValuePlaceholder={entity.entityValuePlaceholder}
|
|
||||||
entityValue={entity.value}
|
|
||||||
isEntityTypeDate={entity.isEntityTypeDate}
|
|
||||||
entityTimeValue={entity.entityTimeValue}
|
|
||||||
onEditEntity={() => editEntity(index)}
|
|
||||||
onSelectDate={(date: Date) => {
|
|
||||||
entityChange(date, index, "value");
|
|
||||||
}}
|
|
||||||
onDeleteEntity={() => deleteEntityAtIndex(index)}
|
|
||||||
onEntityPropertyChange={(event, newInput?: string) => {
|
|
||||||
entityChange(newInput, index, "property");
|
|
||||||
}}
|
|
||||||
onEntityTypeChange={(event: React.FormEvent<HTMLDivElement>, selectedParam: IDropdownOption) => {
|
|
||||||
entityTypeChange(event, selectedParam, index);
|
|
||||||
}}
|
|
||||||
onEntityValueChange={(event, newInput?: string) => {
|
|
||||||
entityChange(newInput, index, "value");
|
|
||||||
}}
|
|
||||||
onEntityTimeValueChange={(event, newInput?: string) => {
|
|
||||||
entityChange(newInput, index, "time");
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{userContext.apiType !== "Cassandra" && (
|
|
||||||
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
|
||||||
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
|
||||||
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="paneFooter">
|
|
||||||
<div className="leftpanel-okbut">
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
onClick={submit}
|
|
||||||
className="genericPaneSubmitBtn"
|
|
||||||
value={getButtonLabel(userContext.apiType)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRenderNavigationContent: IRenderFunction<IPanelProps> = () => {
|
|
||||||
return (
|
|
||||||
<Stack horizontal {...columnProps}>
|
|
||||||
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
|
||||||
<Label>{entityAttributeProperty}</Label>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isEntityValuePanelOpen) {
|
if (isEntityValuePanelOpen) {
|
||||||
return (
|
return (
|
||||||
<PanelContainerComponent
|
<Stack style={{ margin: "20px 0", padding: "0 34px" }}>
|
||||||
headerText=""
|
<Stack horizontal {...columnProps}>
|
||||||
onRenderNavigationContent={onRenderNavigationContent}
|
<Image {...backImageProps} src={RevertBackIcon} alt="back" onClick={() => setIsEntityValuePanelFalse()} />
|
||||||
panelWidth="700px"
|
<Label>{entityAttributeProperty}</Label>
|
||||||
isOpen={true}
|
</Stack>
|
||||||
panelContent={
|
<TextField
|
||||||
<TextField
|
multiline
|
||||||
multiline
|
rows={5}
|
||||||
rows={5}
|
value={entityAttributeValue}
|
||||||
className="entityValueTextField"
|
onChange={(event, newInput?: string) => {
|
||||||
value={entityAttributeValue}
|
entityChange(newInput, selectedRow, "value");
|
||||||
onChange={(event, newInput?: string) => {
|
setEntityAttributeValue(newInput);
|
||||||
entityChange(newInput, selectedRow, "value");
|
}}
|
||||||
setEntityAttributeValue(newInput);
|
/>
|
||||||
}}
|
</Stack>
|
||||||
/>
|
|
||||||
}
|
|
||||||
isConsoleExpanded={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props: RightPaneFormProps = {
|
||||||
|
formError,
|
||||||
|
isExecuting,
|
||||||
|
submitButtonText: getButtonLabel(userContext.apiType),
|
||||||
|
onSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelContainerComponent
|
<RightPaneForm {...props}>
|
||||||
headerText={getPanelTitle(userContext.apiType)}
|
<div className="panelMainContent">
|
||||||
panelWidth="700px"
|
{entities.map((entity, index) => {
|
||||||
isOpen={true}
|
return (
|
||||||
panelContent={renderPanelContent()}
|
<TableEntity
|
||||||
isConsoleExpanded={false}
|
key={"" + entity.id + index}
|
||||||
/>
|
isDeleteOptionVisible={entity.isDeleteOptionVisible}
|
||||||
|
entityTypeLabel={index === 0 && dataTypeLabel}
|
||||||
|
entityPropertyLabel={index === 0 && attributeNameLabel}
|
||||||
|
entityValueLabel={index === 0 && attributeValueLabel}
|
||||||
|
options={userContext.apiType === "Cassandra" ? cassandraOptions : options}
|
||||||
|
isPropertyTypeDisable={entity.isPropertyTypeDisable}
|
||||||
|
entityProperty={entity.property}
|
||||||
|
selectedKey={entity.type}
|
||||||
|
entityPropertyPlaceHolder={detailedHelp}
|
||||||
|
entityValuePlaceholder={entity.entityValuePlaceholder}
|
||||||
|
entityValue={entity.value}
|
||||||
|
isEntityTypeDate={entity.isEntityTypeDate}
|
||||||
|
entityTimeValue={entity.entityTimeValue}
|
||||||
|
onEditEntity={() => editEntity(index)}
|
||||||
|
onSelectDate={(date: Date) => {
|
||||||
|
entityChange(date, index, "value");
|
||||||
|
}}
|
||||||
|
onDeleteEntity={() => deleteEntityAtIndex(index)}
|
||||||
|
onEntityPropertyChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "property");
|
||||||
|
}}
|
||||||
|
onEntityTypeChange={(event: React.FormEvent<HTMLDivElement>, selectedParam: IDropdownOption) => {
|
||||||
|
entityTypeChange(event, selectedParam, index);
|
||||||
|
}}
|
||||||
|
onEntityValueChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "value");
|
||||||
|
}}
|
||||||
|
onEntityTimeValueChange={(event, newInput?: string) => {
|
||||||
|
entityChange(newInput, index, "time");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{userContext.apiType !== "Cassandra" && (
|
||||||
|
<Stack horizontal onClick={addNewEntity} className="addButtonEntiy">
|
||||||
|
<Image {...imageProps} src={AddPropertyIcon} alt="Add Entity" />
|
||||||
|
<Text className="addNewParamStyle">{getAddButtonLabel(userContext.apiType)}</Text>
|
||||||
|
</Stack>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</RightPaneForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -80,7 +80,7 @@ export const int64Placeholder = "Enter a signed 64-bit integer, in the range (-2
|
|||||||
|
|
||||||
export const columnProps: Partial<IStackProps> = {
|
export const columnProps: Partial<IStackProps> = {
|
||||||
tokens: { childrenGap: 10 },
|
tokens: { childrenGap: 10 },
|
||||||
styles: { root: { width: 680 } },
|
styles: { root: { width: 680, marginBottom: 8 } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// helper functions
|
// helper functions
|
||||||
@ -134,8 +134,8 @@ export const getEntityValuePlaceholder = (entityType: string | number): string =
|
|||||||
|
|
||||||
export const isValidEntities = (entities: EntityRowType[]): boolean => {
|
export const isValidEntities = (entities: EntityRowType[]): boolean => {
|
||||||
for (let i = 0; i < entities.length; i++) {
|
for (let i = 0; i < entities.length; i++) {
|
||||||
const { property } = entities[i];
|
const { property, type } = entities[i];
|
||||||
if (property === "" || property === undefined) {
|
if (property === "" || property === undefined || !type) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,13 +170,6 @@ export const getDefaultEntities = (headers: string[], entityTypes: EntityType):
|
|||||||
return defaultEntities;
|
return defaultEntities;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPanelTitle = (apiType: string): string => {
|
|
||||||
if (apiType === "Cassandra") {
|
|
||||||
return "Add Table Row";
|
|
||||||
}
|
|
||||||
return "Add Table Row";
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAddButtonLabel = (apiType: string): string => {
|
export const getAddButtonLabel = (apiType: string): string => {
|
||||||
if (apiType === "Cassandra") {
|
if (apiType === "Cassandra") {
|
||||||
return "Add Row";
|
return "Add Row";
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -137,13 +137,14 @@ export default class QueryTablesTab extends TabsBase {
|
|||||||
useSidePanel
|
useSidePanel
|
||||||
.getState()
|
.getState()
|
||||||
.openSidePanel(
|
.openSidePanel(
|
||||||
"Add Table Entity",
|
"Add Table Row",
|
||||||
<AddTableEntityPanel
|
<AddTableEntityPanel
|
||||||
tableDataClient={this.tableDataClient}
|
tableDataClient={this.tableDataClient}
|
||||||
queryTablesTab={this}
|
queryTablesTab={this}
|
||||||
tableEntityListViewModel={this.tableEntityListViewModel()}
|
tableEntityListViewModel={this.tableEntityListViewModel()}
|
||||||
cassandraApiClient={new CassandraAPIDataClient()}
|
cassandraApiClient={new CassandraAPIDataClient()}
|
||||||
/>
|
/>,
|
||||||
|
"700px"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,14 +2,17 @@ import create, { UseStore } from "zustand";
|
|||||||
|
|
||||||
export interface SidePanelState {
|
export interface SidePanelState {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
panelWidth: string;
|
||||||
panelContent?: JSX.Element;
|
panelContent?: JSX.Element;
|
||||||
headerText?: string;
|
headerText?: string;
|
||||||
openSidePanel: (headerText: string, panelContent: JSX.Element, onClose?: () => void) => void;
|
openSidePanel: (headerText: string, panelContent: JSX.Element, panelWidth?: string, onClose?: () => void) => void;
|
||||||
closeSidePanel: () => void;
|
closeSidePanel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
|
export const useSidePanel: UseStore<SidePanelState> = create((set) => ({
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
openSidePanel: (headerText, panelContent) => set((state) => ({ ...state, headerText, panelContent, isOpen: true })),
|
panelWidth: "440px",
|
||||||
|
openSidePanel: (headerText, panelContent, panelWidth = "440px") =>
|
||||||
|
set((state) => ({ ...state, headerText, panelContent, panelWidth, isOpen: true })),
|
||||||
closeSidePanel: () => set((state) => ({ ...state, isOpen: false })),
|
closeSidePanel: () => set((state) => ({ ...state, isOpen: false })),
|
||||||
}));
|
}));
|
||||||
|
Loading…
Reference in New Issue
Block a user