Initial Move from Azure DevOps to GitHub

This commit is contained in:
Steve Faulkner
2020-05-25 21:30:55 -05:00
commit 36581fb6d9
986 changed files with 195242 additions and 0 deletions

View File

@@ -0,0 +1,288 @@
import * as _ from "underscore";
import * as React from "react";
import * as Constants from "../../../Common/Constants";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import {
DetailsList,
DetailsListLayoutMode,
IDetailsListProps,
IDetailsRowProps,
DetailsRow
} from "office-ui-fabric-react/lib/DetailsList";
import { FocusZone } from "office-ui-fabric-react/lib/FocusZone";
import { IconButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
import { IColumn } from "office-ui-fabric-react/lib/DetailsList";
import { IContextualMenuProps, ContextualMenu } from "office-ui-fabric-react/lib/ContextualMenu";
import {
IObjectWithKey,
ISelectionZoneProps,
Selection,
SelectionMode,
SelectionZone
} from "office-ui-fabric-react/lib/utilities/selection/index";
import { StyleConstants } from "../../../Common/Constants";
import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/lib/TextField";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
export interface QueriesGridComponentProps {
queriesClient: ViewModels.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();
}
}
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>
<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: null,
minWidth: 70,
onRender: (query: Query, index: number, column: IColumn) => {
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: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>, menuItem: any) => {
this.props.onQuerySelect(query);
}
},
{
key: "Delete",
text: "Delete query",
onClick: async (
event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,
menuItem: any
) => {
if (window.confirm("Are you sure you want to delete this query?")) {
const container: ViewModels.Explorer = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title()
});
try {
await this.props.queriesClient.deleteQuery(query);
TelemetryProcessor.traceSuccess(
Action.DeleteSavedQuery,
{
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title()
},
startKey
);
} catch (error) {
TelemetryProcessor.traceFailure(
Action.DeleteSavedQuery,
{
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: container && container.browseQueriesPane.title()
},
startKey
);
}
await this.fetchSavedQueries(); // get latest state
}
}
}
]
},
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
});
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* This adapter is responsible to render the QueriesGrid React component
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
* and update any knockout observables passed from the parent.
*/
import * as ko from "knockout";
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { QueriesGridComponent, QueriesGridComponentProps } from "./QueriesGridComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class QueriesGridComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: ViewModels.Explorer) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
const props: QueriesGridComponentProps = {
queriesClient: this.container.queriesClient,
onQuerySelect: this.container.browseQueriesPane.loadSavedQuery,
containerVisible: this.container.browseQueriesPane.visible(),
saveQueryEnabled: this.container.canSaveQueries()
};
return <QueriesGridComponent {...props} />;
}
public forceRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}