mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-05-09 01:43:33 +01:00
298 lines
9.3 KiB
TypeScript
298 lines
9.3 KiB
TypeScript
import {
|
|
ContextualMenu,
|
|
DetailsList,
|
|
DetailsListLayoutMode,
|
|
DetailsRow,
|
|
FocusZone,
|
|
IButtonProps,
|
|
IColumn,
|
|
IconButton,
|
|
IContextualMenuProps,
|
|
IDetailsListProps,
|
|
IDetailsRowProps,
|
|
IObjectWithKey,
|
|
ISelectionZoneProps,
|
|
ITextField,
|
|
ITextFieldProps,
|
|
Selection,
|
|
SelectionMode,
|
|
SelectionZone,
|
|
TextField,
|
|
} from "@fluentui/react";
|
|
import * as React from "react";
|
|
import * as _ from "underscore";
|
|
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
|
|
import * as Constants from "../../../Common/Constants";
|
|
import { StyleConstants } from "../../../Common/Constants";
|
|
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
|
|
import { QueriesClient } from "../../../Common/QueriesClient";
|
|
import * as DataModels from "../../../Contracts/DataModels";
|
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
import { useDialog } from "../Dialog";
|
|
|
|
const title = "Open Saved Queries";
|
|
|
|
export interface QueriesGridComponentProps {
|
|
queriesClient: QueriesClient;
|
|
onQuerySelect: (query: DataModels.Query) => void;
|
|
containerVisible: boolean;
|
|
saveQueryEnabled: boolean;
|
|
}
|
|
|
|
export interface QueriesGridComponentState {
|
|
queries: Query[];
|
|
filteredResults: Query[];
|
|
}
|
|
|
|
interface Query extends DataModels.Query, IObjectWithKey {
|
|
key: string;
|
|
}
|
|
|
|
export class QueriesGridComponent extends React.Component<QueriesGridComponentProps, QueriesGridComponentState> {
|
|
private selection: Selection;
|
|
private queryFilter: ITextField;
|
|
|
|
constructor(props: QueriesGridComponentProps) {
|
|
super(props);
|
|
this.state = {
|
|
queries: [],
|
|
filteredResults: [],
|
|
};
|
|
this.selection = new Selection();
|
|
this.selection.setItems(this.state.filteredResults);
|
|
}
|
|
|
|
public componentDidUpdate(prevProps: QueriesGridComponentProps, prevState: QueriesGridComponentState): void {
|
|
this.selection.setItems(
|
|
this.state.filteredResults,
|
|
!_.isEqual(prevState.filteredResults, this.state.filteredResults)
|
|
);
|
|
this.queryFilter && this.queryFilter.focus();
|
|
const querySetupCompleted: boolean = !prevProps.saveQueryEnabled && this.props.saveQueryEnabled;
|
|
const noQueryFiltersApplied: boolean = !this.queryFilter || !this.queryFilter.value;
|
|
if (!this.props.containerVisible || !this.props.saveQueryEnabled) {
|
|
return;
|
|
} else if (noQueryFiltersApplied && (!prevProps.containerVisible || querySetupCompleted)) {
|
|
// refresh only when pane is opened or query setup was recently completed
|
|
this.fetchSavedQueries();
|
|
}
|
|
}
|
|
|
|
// fetched saved queries when panel open
|
|
public componentDidMount() {
|
|
this.fetchSavedQueries();
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
if (this.state.queries.length === 0) {
|
|
return this.renderBannerComponent();
|
|
}
|
|
return this.renderQueryGridComponent();
|
|
}
|
|
|
|
private renderQueryGridComponent(): JSX.Element {
|
|
const searchFilterProps: ITextFieldProps = {
|
|
placeholder: "Search for Queries",
|
|
ariaLabel: "Query filter input",
|
|
onChange: this.onFilterInputChange,
|
|
componentRef: (queryInput: ITextField) => (this.queryFilter = queryInput),
|
|
styles: {
|
|
root: { paddingBottom: "12px" },
|
|
field: { fontSize: `${StyleConstants.mediumFontSize}px` },
|
|
},
|
|
};
|
|
const selectionContainerProps: ISelectionZoneProps = {
|
|
selection: this.selection,
|
|
selectionMode: SelectionMode.single,
|
|
onItemInvoked: (item: Query) => this.props.onQuerySelect(item),
|
|
};
|
|
const detailsListProps: IDetailsListProps = {
|
|
items: this.state.filteredResults,
|
|
columns: this.getColumns(),
|
|
isHeaderVisible: false,
|
|
setKey: "queryName",
|
|
layoutMode: DetailsListLayoutMode.fixedColumns,
|
|
selection: this.selection,
|
|
selectionMode: SelectionMode.none,
|
|
compact: true,
|
|
onRenderRow: this.onRenderRow,
|
|
styles: {
|
|
root: { width: "100%" },
|
|
},
|
|
};
|
|
|
|
return (
|
|
<FocusZone style={{ width: "100%" }}>
|
|
<TextField {...searchFilterProps} />
|
|
<SelectionZone {...selectionContainerProps}>
|
|
<DetailsList {...detailsListProps} />
|
|
</SelectionZone>
|
|
</FocusZone>
|
|
);
|
|
}
|
|
|
|
private renderBannerComponent(): JSX.Element {
|
|
const bannerProps: React.ImgHTMLAttributes<HTMLImageElement> = {
|
|
src: SaveQueryBannerIcon,
|
|
alt: "Save query helper banner",
|
|
style: {
|
|
height: "150px",
|
|
width: "310px",
|
|
marginTop: "20px",
|
|
border: `1px solid ${StyleConstants.BaseMedium}`,
|
|
},
|
|
};
|
|
return (
|
|
<div id="emptyQueryBanner">
|
|
<div>
|
|
You have not saved any queries yet. <br /> <br />
|
|
To write a new query, open a new query tab and enter the desired query. Once ready to save, click on Save
|
|
Query and follow the prompt in order to save the query.
|
|
</div>
|
|
<img {...bannerProps} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
private onFilterInputChange = (event: React.FormEvent<HTMLInputElement>, query: string): void => {
|
|
if (query) {
|
|
const filteredQueries: Query[] = this.state.queries.filter(
|
|
(savedQuery: Query) =>
|
|
savedQuery.queryName.indexOf(query) > -1 || savedQuery.queryName.toLowerCase().indexOf(query) > -1
|
|
);
|
|
this.setState({
|
|
filteredResults: filteredQueries,
|
|
});
|
|
} else {
|
|
// no filter
|
|
this.setState({
|
|
filteredResults: this.state.queries,
|
|
});
|
|
}
|
|
};
|
|
|
|
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
|
|
props.styles = {
|
|
root: { width: "100%" },
|
|
fields: {
|
|
width: "100%",
|
|
justifyContent: "space-between",
|
|
},
|
|
cell: {
|
|
margin: "auto 0",
|
|
},
|
|
};
|
|
return <DetailsRow data-selection-invoke={true} {...props} />;
|
|
};
|
|
|
|
private getColumns(): IColumn[] {
|
|
return [
|
|
{
|
|
key: "Name",
|
|
name: "Name",
|
|
fieldName: "queryName",
|
|
minWidth: 260,
|
|
},
|
|
{
|
|
key: "Action",
|
|
name: "Action",
|
|
fieldName: undefined,
|
|
minWidth: 70,
|
|
onRender: (query: Query) => {
|
|
const buttonProps: IButtonProps = {
|
|
iconProps: {
|
|
iconName: "More",
|
|
title: "More",
|
|
ariaLabel: "More actions button",
|
|
},
|
|
menuIconProps: {
|
|
styles: { root: { display: "none" } },
|
|
},
|
|
menuProps: {
|
|
isBeakVisible: true,
|
|
items: [
|
|
{
|
|
key: "Open",
|
|
text: "Open query",
|
|
onClick: () => {
|
|
this.props.onQuerySelect(query);
|
|
},
|
|
},
|
|
{
|
|
key: "Delete",
|
|
text: "Delete query",
|
|
onClick: async () => {
|
|
useDialog.getState().showOkCancelModalDialog(
|
|
"Confirm delete",
|
|
"Are you sure you want to delete this query?",
|
|
"Delete",
|
|
async () => {
|
|
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
|
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
paneTitle: title,
|
|
});
|
|
try {
|
|
await this.props.queriesClient.deleteQuery(query);
|
|
TelemetryProcessor.traceSuccess(
|
|
Action.DeleteSavedQuery,
|
|
{
|
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
paneTitle: title,
|
|
},
|
|
startKey
|
|
);
|
|
} catch (error) {
|
|
TelemetryProcessor.traceFailure(
|
|
Action.DeleteSavedQuery,
|
|
{
|
|
dataExplorerArea: Constants.Areas.ContextualPane,
|
|
paneTitle: title,
|
|
error: getErrorMessage(error),
|
|
errorStack: getErrorStack(error),
|
|
},
|
|
startKey
|
|
);
|
|
}
|
|
await this.fetchSavedQueries(); // get latest state
|
|
},
|
|
"Cancel",
|
|
undefined
|
|
);
|
|
},
|
|
},
|
|
],
|
|
},
|
|
menuAs: (menuProps: IContextualMenuProps): JSX.Element => {
|
|
return <ContextualMenu {...menuProps} />;
|
|
},
|
|
};
|
|
return <IconButton {...buttonProps} />;
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
private async fetchSavedQueries(): Promise<void> {
|
|
let queries: Query[];
|
|
try {
|
|
queries = (await this.props.queriesClient.getQueries()) as Query[];
|
|
} catch (error) {
|
|
console.error(error);
|
|
return;
|
|
}
|
|
queries = queries.map((query: Query) => {
|
|
query.key = query.queryName;
|
|
return query;
|
|
});
|
|
|
|
// we do a deep equality check before setting the state to avoid infinite re-renders
|
|
if (!_.isEqual(queries, this.state.queries)) {
|
|
this.setState({
|
|
filteredResults: queries,
|
|
queries: queries,
|
|
});
|
|
}
|
|
}
|
|
}
|