Prettier 2.0 (#393)

This commit is contained in:
Steve Faulkner
2021-01-20 09:15:01 -06:00
committed by GitHub
parent c1937ca464
commit 4be53284b5
500 changed files with 41927 additions and 41838 deletions

View File

@@ -1,122 +1,122 @@
jest.mock("monaco-editor");
import * as ko from "knockout";
import "./ComponentRegisterer";
describe("Component Registerer", () => {
it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
});
it("should register new-vertex-form component", () => {
expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
});
it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true);
});
it("should register graph-style component", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true);
});
it("should register collapsible-panel component", () => {
expect(ko.components.isRegistered("collapsible-panel")).toBe(true);
});
it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true);
});
it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
});
it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true);
});
it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
});
it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true);
});
it("should register spark-master-tab component", () => {
expect(ko.components.isRegistered("spark-master-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
it("should register delete-collection-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
});
it("should register delete-database-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-database-confirmation-pane")).toBe(true);
});
it("should register save-query-pane component", () => {
expect(ko.components.isRegistered("save-query-pane")).toBe(true);
});
it("should register browse-queries-pane component", () => {
expect(ko.components.isRegistered("browse-queries-pane")).toBe(true);
});
it("should register graph-new-vertex-pane component", () => {
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
});
it("should register upload-file-pane component", () => {
expect(ko.components.isRegistered("upload-file-pane")).toBe(true);
});
it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
});
it("should register setup-notebooks-pane component", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});
});
jest.mock("monaco-editor");
import * as ko from "knockout";
import "./ComponentRegisterer";
describe("Component Registerer", () => {
it("should register input-typeahead component", () => {
expect(ko.components.isRegistered("input-typeahead")).toBe(true);
});
it("should register new-vertex-form component", () => {
expect(ko.components.isRegistered("new-vertex-form")).toBe(true);
});
it("should register error-display component", () => {
expect(ko.components.isRegistered("error-display")).toBe(true);
});
it("should register graph-style component", () => {
expect(ko.components.isRegistered("graph-style")).toBe(true);
});
it("should register collapsible-panel component", () => {
expect(ko.components.isRegistered("collapsible-panel")).toBe(true);
});
it("should register json-editor component", () => {
expect(ko.components.isRegistered("json-editor")).toBe(true);
});
it("should register documents-tab component", () => {
expect(ko.components.isRegistered("documents-tab")).toBe(true);
});
it("should register stored-procedure-tab component", () => {
expect(ko.components.isRegistered("stored-procedure-tab")).toBe(true);
});
it("should register trigger-tab component", () => {
expect(ko.components.isRegistered("trigger-tab")).toBe(true);
});
it("should register user-defined-function-tab component", () => {
expect(ko.components.isRegistered("user-defined-function-tab")).toBe(true);
});
it("should register settings-tab-v2 component", () => {
expect(ko.components.isRegistered("settings-tab-v2")).toBe(true);
});
it("should register query-tab component", () => {
expect(ko.components.isRegistered("query-tab")).toBe(true);
});
it("should register tables-query-tab component", () => {
expect(ko.components.isRegistered("tables-query-tab")).toBe(true);
});
it("should register graph-tab component", () => {
expect(ko.components.isRegistered("graph-tab")).toBe(true);
});
it("should register notebookv2-tab component", () => {
expect(ko.components.isRegistered("notebookv2-tab")).toBe(true);
});
it("should register terminal-tab component", () => {
expect(ko.components.isRegistered("terminal-tab")).toBe(true);
});
it("should register spark-master-tab component", () => {
expect(ko.components.isRegistered("spark-master-tab")).toBe(true);
});
it("should register mongo-shell-tab component", () => {
expect(ko.components.isRegistered("mongo-shell-tab")).toBe(true);
});
it("should registeradd-collection-pane component", () => {
expect(ko.components.isRegistered("add-collection-pane")).toBe(true);
});
it("should register delete-collection-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-collection-confirmation-pane")).toBe(true);
});
it("should register delete-database-confirmation-pane component", () => {
expect(ko.components.isRegistered("delete-database-confirmation-pane")).toBe(true);
});
it("should register save-query-pane component", () => {
expect(ko.components.isRegistered("save-query-pane")).toBe(true);
});
it("should register browse-queries-pane component", () => {
expect(ko.components.isRegistered("browse-queries-pane")).toBe(true);
});
it("should register graph-new-vertex-pane component", () => {
expect(ko.components.isRegistered("graph-new-vertex-pane")).toBe(true);
});
it("should register graph-styling-pane component", () => {
expect(ko.components.isRegistered("graph-styling-pane")).toBe(true);
});
it("should register upload-file-pane component", () => {
expect(ko.components.isRegistered("upload-file-pane")).toBe(true);
});
it("should register string-input-pane component", () => {
expect(ko.components.isRegistered("string-input-pane")).toBe(true);
});
it("should register setup-notebooks-pane component", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});
});

View File

@@ -1,77 +1,77 @@
import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2());
ko.components.register("query-tab", new TabComponents.QueryTab());
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab());
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
// Database Tabs
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
// Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register(
"delete-collection-confirmation-pane",
new PaneComponents.DeleteCollectionConfirmationPaneComponent()
);
ko.components.register(
"delete-database-confirmation-pane",
new PaneComponents.DeleteDatabaseConfirmationPaneComponent()
);
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent());
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent());
ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent());
ko.components.register("renew-adhoc-access-pane", new PaneComponents.RenewAdHocAccessPane());
ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());
ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent());
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());
import * as ko from "knockout";
import * as PaneComponents from "./Panes/PaneComponents";
import * as TabComponents from "./Tabs/TabComponents";
import { CollapsiblePanelComponent } from "./Controls/CollapsiblePanel/CollapsiblePanelComponent";
import { DiffEditorComponent } from "./Controls/DiffEditor/DiffEditorComponent";
import { DynamicListComponent } from "./Controls/DynamicList/DynamicListComponent";
import { EditorComponent } from "./Controls/Editor/EditorComponent";
import { ErrorDisplayComponent } from "./Controls/ErrorDisplayComponent/ErrorDisplayComponent";
import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleComponent";
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
ko.components.register("input-typeahead", new InputTypeaheadComponent());
ko.components.register("new-vertex-form", NewVertexComponent);
ko.components.register("error-display", new ErrorDisplayComponent());
ko.components.register("graph-style", GraphStyleComponent);
ko.components.register("collapsible-panel", new CollapsiblePanelComponent());
ko.components.register("editor", new EditorComponent());
ko.components.register("json-editor", new JsonEditorComponent());
ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
ko.components.register("mongo-documents-tab", new TabComponents.MongoDocumentsTab());
ko.components.register("stored-procedure-tab", new TabComponents.StoredProcedureTab());
ko.components.register("trigger-tab", new TabComponents.TriggerTab());
ko.components.register("user-defined-function-tab", new TabComponents.UserDefinedFunctionTab());
ko.components.register("settings-tab-v2", new TabComponents.SettingsTabV2());
ko.components.register("query-tab", new TabComponents.QueryTab());
ko.components.register("tables-query-tab", new TabComponents.QueryTablesTab());
ko.components.register("graph-tab", new TabComponents.GraphTab());
ko.components.register("mongo-shell-tab", new TabComponents.MongoShellTab());
ko.components.register("conflicts-tab", new TabComponents.ConflictsTab());
ko.components.register("notebookv2-tab", new TabComponents.NotebookV2Tab());
ko.components.register("terminal-tab", new TabComponents.TerminalTab());
ko.components.register("spark-master-tab", new TabComponents.SparkMasterTab());
ko.components.register("gallery-tab", new TabComponents.GalleryTab());
ko.components.register("notebook-viewer-tab", new TabComponents.NotebookViewerTab());
// Database Tabs
ko.components.register("database-settings-tab", new TabComponents.DatabaseSettingsTab());
// Panes
ko.components.register("add-database-pane", new PaneComponents.AddDatabasePaneComponent());
ko.components.register("add-collection-pane", new PaneComponents.AddCollectionPaneComponent());
ko.components.register(
"delete-collection-confirmation-pane",
new PaneComponents.DeleteCollectionConfirmationPaneComponent()
);
ko.components.register(
"delete-database-confirmation-pane",
new PaneComponents.DeleteDatabaseConfirmationPaneComponent()
);
ko.components.register("graph-new-vertex-pane", new PaneComponents.GraphNewVertexPaneComponent());
ko.components.register("graph-styling-pane", new PaneComponents.GraphStylingPaneComponent());
ko.components.register("table-add-entity-pane", new PaneComponents.TableAddEntityPaneComponent());
ko.components.register("table-edit-entity-pane", new PaneComponents.TableEditEntityPaneComponent());
ko.components.register("table-column-options-pane", new PaneComponents.TableColumnOptionsPaneComponent());
ko.components.register("table-query-select-pane", new PaneComponents.TableQuerySelectPaneComponent());
ko.components.register("cassandra-add-collection-pane", new PaneComponents.CassandraAddCollectionPaneComponent());
ko.components.register("settings-pane", new PaneComponents.SettingsPaneComponent());
ko.components.register("execute-sproc-params-pane", new PaneComponents.ExecuteSprocParamsComponent());
ko.components.register("renew-adhoc-access-pane", new PaneComponents.RenewAdHocAccessPane());
ko.components.register("upload-items-pane", new PaneComponents.UploadItemsPaneComponent());
ko.components.register("load-query-pane", new PaneComponents.LoadQueryPaneComponent());
ko.components.register("save-query-pane", new PaneComponents.SaveQueryPaneComponent());
ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPaneComponent());
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -36,8 +36,8 @@ export class ResourceTreeContextMenuButtonFactory {
{
iconSrc: AddCollectionIcon,
onClick: () => container.onNewCollectionClicked(),
label: container.addCollectionText()
}
label: container.addCollectionText(),
},
];
if (userContext.defaultExperience !== DefaultAccountExperienceType.Table) {
@@ -45,7 +45,7 @@ export class ResourceTreeContextMenuButtonFactory {
iconSrc: DeleteDatabaseIcon,
onClick: () => container.deleteDatabaseConfirmationPane.open(),
label: container.deleteDatabaseText(),
styleClass: "deleteDatabaseMenuItem"
styleClass: "deleteDatabaseMenuItem",
});
}
return items;
@@ -60,7 +60,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewQueryClick(selectedCollection, null),
label: "New SQL Query"
label: "New SQL Query",
});
}
@@ -68,7 +68,7 @@ export class ResourceTreeContextMenuButtonFactory {
items.push({
iconSrc: AddSqlQueryIcon,
onClick: () => selectedCollection && selectedCollection.onNewMongoQueryClick(selectedCollection, null),
label: "New Query"
label: "New Query",
});
items.push({
@@ -77,7 +77,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewMongoShellClick();
},
label: "New Shell"
label: "New Shell",
});
}
@@ -88,7 +88,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewStoredProcedureClick(selectedCollection, null);
},
label: "New Stored Procedure"
label: "New Stored Procedure",
});
items.push({
@@ -97,7 +97,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewUserDefinedFunctionClick(selectedCollection, null);
},
label: "New UDF"
label: "New UDF",
});
items.push({
@@ -106,7 +106,7 @@ export class ResourceTreeContextMenuButtonFactory {
const selectedCollection: ViewModels.Collection = container.findSelectedCollection();
selectedCollection && selectedCollection.onNewTriggerClick(selectedCollection, null);
},
label: "New Trigger"
label: "New Trigger",
});
}
@@ -117,7 +117,7 @@ export class ResourceTreeContextMenuButtonFactory {
selectedCollection && selectedCollection.onDeleteCollectionContextMenuClick(selectedCollection, null);
},
label: container.deleteCollectionText(),
styleClass: "deleteCollectionMenuItem"
styleClass: "deleteCollectionMenuItem",
});
return items;
@@ -135,8 +135,8 @@ export class ResourceTreeContextMenuButtonFactory {
{
iconSrc: DeleteSprocIcon,
onClick: () => storedProcedure.delete(),
label: "Delete Store Procedure"
}
label: "Delete Store Procedure",
},
];
}
@@ -149,8 +149,8 @@ export class ResourceTreeContextMenuButtonFactory {
{
iconSrc: DeleteTriggerIcon,
onClick: () => trigger.delete(),
label: "Delete Trigger"
}
label: "Delete Trigger",
},
];
}
@@ -166,8 +166,8 @@ export class ResourceTreeContextMenuButtonFactory {
{
iconSrc: DeleteUDFIcon,
onClick: () => userDefinedFunction.delete(),
label: "Delete User Defined Function"
}
label: "Delete User Defined Function",
},
];
}
}

View File

@@ -31,7 +31,7 @@ export class AccessibleElement extends React.Component<AccessibleElementProps> {
...elementProps,
onKeyPress: this.onKeyPress,
onClick: this.props.onActivated,
tabIndex
tabIndex,
});
}
}

View File

@@ -38,7 +38,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
super(props);
this.isExpanded = props.isExpanded;
this.state = {
isExpanded: true
isExpanded: true,
};
}
@@ -46,7 +46,7 @@ export class AccordionItemComponent extends React.Component<AccordionItemCompone
if (this.props.isExpanded !== this.isExpanded) {
this.isExpanded = this.props.isExpanded;
this.setState({
isExpanded: this.props.isExpanded
isExpanded: this.props.isExpanded,
});
}
}

View File

@@ -30,7 +30,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
constructor(props: ArcadiaMenuPickerProps) {
super(props);
this.state = {
selectedSparkPool: props.selectedSparkPool
selectedSparkPool: props.selectedSparkPool,
};
}
@@ -41,7 +41,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
try {
this.props.onSparkPoolSelect(e, item);
this.setState({
selectedSparkPool: item.text
selectedSparkPool: item.text,
});
} catch (error) {
Logger.logError(getErrorMessage(error), "ArcadiaMenuPicker/_onSparkPoolClicked");
@@ -65,28 +65,28 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
public render() {
const { workspaces } = this.props;
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map(workspace => {
let workspaceMenuItems: IContextualMenuItem[] = workspaces.map((workspace) => {
let sparkPoolsMenuProps: IContextualMenuProps = {
items: workspace.sparkPools.map(
(sparkpool): IContextualMenuItem => ({
key: sparkpool.id,
text: sparkpool.name,
onClick: this._onSparkPoolClicked
onClick: this._onSparkPoolClicked,
})
)
),
};
if (!sparkPoolsMenuProps.items.length) {
sparkPoolsMenuProps.items.push({
key: workspace.id,
text: "Create new spark pool",
onClick: this._onCreateNewSparkPoolClicked
onClick: this._onCreateNewSparkPoolClicked,
});
}
return {
key: workspace.id,
text: workspace.name,
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps
subMenuProps: this.props.disableSubmenu ? undefined : sparkPoolsMenuProps,
};
});
@@ -94,7 +94,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
workspaceMenuItems.push({
key: "create_workspace",
text: "Create new workspace",
onClick: this._onCreateNewWorkspaceClicked
onClick: this._onCreateNewWorkspaceClicked,
});
}
@@ -103,29 +103,29 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
backgroundColor: "transparent",
margin: "auto 5px",
padding: "0",
border: "0"
border: "0",
},
rootHovered: {
backgroundColor: "transparent"
backgroundColor: "transparent",
},
rootChecked: {
backgroundColor: "transparent"
backgroundColor: "transparent",
},
rootFocused: {
backgroundColor: "transparent"
backgroundColor: "transparent",
},
rootExpanded: {
backgroundColor: "transparent"
backgroundColor: "transparent",
},
flexContainer: {
height: "30px",
border: "1px solid #a6a6a6",
padding: "0 8px"
padding: "0 8px",
},
label: {
fontWeight: "400",
fontSize: "12px"
}
fontSize: "12px",
},
};
return (
@@ -134,7 +134,7 @@ export class ArcadiaMenuPicker extends React.Component<ArcadiaMenuPickerProps, A
persistMenu={true}
className="arcadia-menu-picker"
menuProps={{
items: workspaceMenuItems
items: workspaceMenuItems,
}}
styles={dropdownStyle}
/>

View File

@@ -1,56 +1,56 @@
import * as ko from "knockout";
import template from "./collapsible-panel-component.html";
/**
* Helper class for ko component registration
*/
export class CollapsiblePanelComponent {
constructor() {
return {
viewModel: CollapsiblePanelViewModel,
template
};
}
}
/**
* Parameters for this component
*/
interface CollapsiblePanelParams {
collapsedTitle: ko.Observable<string>;
expandedTitle: ko.Observable<string>;
isCollapsed?: ko.Observable<boolean>;
collapseToLeft?: boolean;
}
/**
* Collapsible panel:
* Contains a header with [>] button to collapse and an title ("expandedTitle").
* Collapsing the panel:
* - shrinks width to narrow amount
* - hides children
* - shows [<]
* - shows vertical title ("collapsedTitle")
* - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter)
*
* How to use in your markup:
* <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }">
* <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) -->
* </collapsible-panel>
*
* Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component.
* Use the optional "collapseToLeft" parameter to collapse to the left.
*/
class CollapsiblePanelViewModel {
public params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) {
this.params = params;
this.isCollapsed = params.isCollapsed || ko.observable(false);
}
public toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed());
}
}
import * as ko from "knockout";
import template from "./collapsible-panel-component.html";
/**
* Helper class for ko component registration
*/
export class CollapsiblePanelComponent {
constructor() {
return {
viewModel: CollapsiblePanelViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
interface CollapsiblePanelParams {
collapsedTitle: ko.Observable<string>;
expandedTitle: ko.Observable<string>;
isCollapsed?: ko.Observable<boolean>;
collapseToLeft?: boolean;
}
/**
* Collapsible panel:
* Contains a header with [>] button to collapse and an title ("expandedTitle").
* Collapsing the panel:
* - shrinks width to narrow amount
* - hides children
* - shows [<]
* - shows vertical title ("collapsedTitle")
* - the default behavior is to collapse to the right (ie, place this component on the right or use "collapseToLeft" parameter)
*
* How to use in your markup:
* <collapsible-panel params="{ collapsedTitle:'Properties', expandedTitle:'Expanded properties' }">
* <!-- add your markup here: the ko context is the same as outside of collapsible-panel (ie $data) -->
* </collapsible-panel>
*
* Use the optional "isCollapsed" parameter to programmatically collapse/expand the pane from outside the component.
* Use the optional "collapseToLeft" parameter to collapse to the left.
*/
class CollapsiblePanelViewModel {
public params: CollapsiblePanelParams;
private isCollapsed: ko.Observable<boolean>;
public constructor(params: CollapsiblePanelParams) {
this.params = params;
this.isCollapsed = params.isCollapsed || ko.observable(false);
}
public toggleCollapse(): void {
this.isCollapsed(!this.isCollapsed());
}
}

View File

@@ -5,7 +5,7 @@ import { CollapsibleSectionComponent, CollapsibleSectionProps } from "./Collapsi
describe("CollapsibleSectionComponent", () => {
it("renders", () => {
const props: CollapsibleSectionProps = {
title: "Sample title"
title: "Sample title",
};
const wrapper = shallow(<CollapsibleSectionComponent {...props} />);

View File

@@ -14,7 +14,7 @@ export class CollapsibleSectionComponent extends React.Component<CollapsibleSect
constructor(props: CollapsibleSectionProps) {
super(props);
this.state = {
isExpanded: true
isExpanded: true,
};
}

View File

@@ -1,44 +1,44 @@
<div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }">
<div class="panelHeader" data-bind="visible: !isCollapsed()">
<span
class="collapsedIconContainer collapseExpandButton"
data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }"
>
<img
class="collapsedIcon imgVerticalAlignment"
src="/imgarrowlefticon.svg"
alt="Collapse"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
/>
</span>
<span
class="expandedTitle"
data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }"
></span>
</div>
<div class="collapsibleNav nav" data-bind="visible:isCollapsed">
<ul class="nav">
<li class="collapsedBtn collapseExpandButton">
<span class="collapsedIconContainer" data-bind="click: toggleCollapse">
<img
class="collapsedIcon"
src="/imgarrowlefticon.svg"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
alt="Expand"
/>
</span>
<span class="rotatedInner" data-bind="click: toggleCollapse">
<span data-bind="text: params.collapsedTitle"></span>
</span>
</li>
</ul>
</div>
<div class="panelContent" data-bind="visible:!isCollapsed()">
<!-- ko with:$parent -->
<!-- ko template: { nodes: $componentTemplateNodes } -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>
<div class="collapsiblePanel" data-bind="css: { paneCollapsed:isCollapsed() }">
<div class="panelHeader" data-bind="visible: !isCollapsed()">
<span
class="collapsedIconContainer collapseExpandButton"
data-bind="click:toggleCollapse, css: { 'pull-right':params.collapseToLeft }"
>
<img
class="collapsedIcon imgVerticalAlignment"
src="/imgarrowlefticon.svg"
alt="Collapse"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
/>
</span>
<span
class="expandedTitle"
data-bind="text: params.expandedTitle, css:{ iconSpacer:!params.collapseToLeft }"
></span>
</div>
<div class="collapsibleNav nav" data-bind="visible:isCollapsed">
<ul class="nav">
<li class="collapsedBtn collapseExpandButton">
<span class="collapsedIconContainer" data-bind="click: toggleCollapse">
<img
class="collapsedIcon"
src="/imgarrowlefticon.svg"
data-bind="css: { expanded:!isCollapsed(), iconMirror:params.collapseToLeft }"
alt="Expand"
/>
</span>
<span class="rotatedInner" data-bind="click: toggleCollapse">
<span data-bind="text: params.collapsedTitle"></span>
</span>
</li>
</ul>
</div>
<div class="panelContent" data-bind="visible:!isCollapsed()">
<!-- ko with:$parent -->
<!-- ko template: { nodes: $componentTemplateNodes } -->
<!-- /ko -->
<!-- /ko -->
</div>
</div>

View File

@@ -149,9 +149,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
private onLauncherKeyDown(event: React.KeyboardEvent<HTMLDivElement>): boolean {
if (event.keyCode === KeyCodes.DownArrow) {
$(this.dropdownElt).hide();
$(this.dropdownElt)
.show()
.focus();
$(this.dropdownElt).show().focus();
event.stopPropagation();
return false;
}
@@ -187,7 +185,7 @@ export class CommandButtonComponent extends React.Component<CommandButtonCompone
}
this.props.onCommandClick(e);
TelemetryProcessor.trace(Action.SelectItem, ActionModifiers.Mark, {
commandButtonClicked: this.props.commandButtonLabel
commandButtonClicked: this.props.commandButtonLabel,
});
}

View File

@@ -1,101 +1,101 @@
import * as React from "react";
import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps {
label: string;
multiline: boolean;
autoAdjustHeight: boolean;
rows: number;
onChange: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
defaultValue?: string;
}
export interface LinkProps {
linkText: string;
linkUrl: string;
}
export interface DialogProps {
title: string;
subText: string;
isModal: boolean;
visible: boolean;
choiceGroupProps?: IChoiceGroupProps;
textFieldProps?: TextFieldProps;
linkProps?: LinkProps;
primaryButtonText: string;
secondaryButtonText: string;
onPrimaryButtonClick: () => void;
onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean;
type?: DialogType;
showCloseButton?: boolean;
onDismiss?: () => void;
}
const DIALOG_MIN_WIDTH = "400px";
const DIALOG_MAX_WIDTH = "600px";
const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export class DialogComponent extends React.Component<DialogProps, {}> {
constructor(props: DialogProps) {
super(props);
}
public render(): JSX.Element {
const dialogProps: IDialogProps = {
hidden: !this.props.visible,
dialogContentProps: {
type: this.props.type || DialogType.normal,
title: this.props.title,
subText: this.props.subText,
styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
},
showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss
},
modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH
};
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText,
disabled: this.props.primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick
};
const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick
? {
text: this.props.secondaryButtonText,
onClick: this.props.onSecondaryButtonClick
}
: undefined;
return (
<Dialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link>
)}
<DialogFooter>
<PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter>
</Dialog>
);
}
}
import * as React from "react";
import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric-react/lib/Dialog";
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import { ChoiceGroup, FontIcon, IChoiceGroupProps } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps {
label: string;
multiline: boolean;
autoAdjustHeight: boolean;
rows: number;
onChange: (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => void;
defaultValue?: string;
}
export interface LinkProps {
linkText: string;
linkUrl: string;
}
export interface DialogProps {
title: string;
subText: string;
isModal: boolean;
visible: boolean;
choiceGroupProps?: IChoiceGroupProps;
textFieldProps?: TextFieldProps;
linkProps?: LinkProps;
primaryButtonText: string;
secondaryButtonText: string;
onPrimaryButtonClick: () => void;
onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean;
type?: DialogType;
showCloseButton?: boolean;
onDismiss?: () => void;
}
const DIALOG_MIN_WIDTH = "400px";
const DIALOG_MAX_WIDTH = "600px";
const DIALOG_TITLE_FONT_SIZE = "17px";
const DIALOG_TITLE_FONT_WEIGHT = 400;
const DIALOG_SUBTEXT_FONT_SIZE = "15px";
export class DialogComponent extends React.Component<DialogProps, {}> {
constructor(props: DialogProps) {
super(props);
}
public render(): JSX.Element {
const dialogProps: IDialogProps = {
hidden: !this.props.visible,
dialogContentProps: {
type: this.props.type || DialogType.normal,
title: this.props.title,
subText: this.props.subText,
styles: {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE },
},
showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss,
},
modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH,
maxWidth: DIALOG_MAX_WIDTH,
};
const choiceGroupProps: IChoiceGroupProps = this.props.choiceGroupProps;
const textFieldProps: ITextFieldProps = this.props.textFieldProps;
const linkProps: LinkProps = this.props.linkProps;
const primaryButtonProps: IButtonProps = {
text: this.props.primaryButtonText,
disabled: this.props.primaryButtonDisabled || false,
onClick: this.props.onPrimaryButtonClick,
};
const secondaryButtonProps: IButtonProps =
this.props.secondaryButtonText && this.props.onSecondaryButtonClick
? {
text: this.props.secondaryButtonText,
onClick: this.props.onSecondaryButtonClick,
}
: undefined;
return (
<Dialog {...dialogProps}>
{choiceGroupProps && <ChoiceGroup {...choiceGroupProps} />}
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link>
)}
<DialogFooter>
<PrimaryButton {...primaryButtonProps} />
{secondaryButtonProps && <DefaultButton {...secondaryButtonProps} />}
</DialogFooter>
</Dialog>
);
}
}

View File

@@ -1,16 +1,16 @@
/**
* This adapter is responsible to render the Dialog 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 React from "react";
import { DialogComponent, DialogProps } from "./DialogComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DialogComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<DialogProps>;
public renderComponent(): JSX.Element {
return <DialogComponent {...this.parameters()} />;
}
}
/**
* This adapter is responsible to render the Dialog 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 React from "react";
import { DialogComponent, DialogProps } from "./DialogComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class DialogComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<DialogProps>;
public renderComponent(): JSX.Element {
return <DialogComponent {...this.parameters()} />;
}
}

View File

@@ -9,7 +9,7 @@ export class DiffEditorComponent {
constructor() {
return {
viewModel: DiffEditorViewModel,
template
template,
};
}
}
@@ -103,7 +103,7 @@ export class DiffEditorViewModel {
lineNumbers: this.params.lineNumbers || "off",
fontSize: 12,
ariaLabel: this.params.ariaLabel,
theme: this.params.theme
theme: this.params.theme,
};
if (this.params.renderSideBySide !== undefined) {
@@ -120,7 +120,7 @@ export class DiffEditorViewModel {
);
diffEditor.setModel({
original: originalModel,
modified: modifiedModel
modified: modifiedModel,
});
createCallback(diffEditor);
@@ -147,7 +147,7 @@ export class DiffEditorViewModel {
this.observer.observe(document.body, {
attributes: true,
subtree: true,
childList: true
childList: true,
});
this.editor.focus();
}

View File

@@ -7,7 +7,7 @@ const createBlankProps = (): DefaultDirectoryDropdownProps => {
return {
defaultDirectoryId: "",
directories: [],
onDefaultDirectoryChange: jest.fn()
onDefaultDirectoryChange: jest.fn(),
};
};
@@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: "",
domains: [],
id: "",
tenantId: ""
tenantId: "",
};
};
@@ -90,27 +90,15 @@ describe("test function", () => {
const wrapper = mount(<DefaultDirectoryDropdownComponent {...props} />);
wrapper
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper
.find("button.ms-Dropdown-item")
.at(1)
.simulate("click");
wrapper.find("button.ms-Dropdown-item").at(1).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
wrapper
.find("div.defaultDirectoryDropdown")
.find("div.ms-Dropdown")
.simulate("click");
wrapper.find("div.defaultDirectoryDropdown").find("div.ms-Dropdown").simulate("click");
expect(wrapper.exists("div.ms-Callout-main")).toBe(true);
wrapper
.find("button.ms-Dropdown-item")
.at(0)
.simulate("click");
wrapper.find("button.ms-Dropdown-item").at(0).simulate("click");
expect(props.onDefaultDirectoryChange).toBeCalled();
expect(props.onDefaultDirectoryChange).toHaveBeenCalled();
});

View File

@@ -19,13 +19,13 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
public render(): JSX.Element {
const lastVisitedOption: IDropdownOption = {
key: DefaultDirectoryDropdownComponent.lastVisitedKey,
text: "Sign in to your last visited directory"
text: "Sign in to your last visited directory",
};
const directoryOptions: Array<IDropdownOption> = this.props.directories.map(
(dirc): IDropdownOption => {
return {
key: dirc.tenantId,
text: `${dirc.displayName}(${dirc.tenantId})`
text: `${dirc.displayName}(${dirc.tenantId})`,
};
}
);
@@ -35,7 +35,7 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
options: dropDownOptions,
defaultSelectedKey: this.props.defaultDirectoryId ? this.props.defaultDirectoryId : lastVisitedOption.key,
onChange: this._onDropdownChange,
className: "defaultDirectoryDropdown"
className: "defaultDirectoryDropdown",
};
return <Dropdown {...dropDownProps} />;
@@ -56,12 +56,12 @@ export class DefaultDirectoryDropdownComponent extends React.Component<DefaultDi
countryCode: undefined,
displayName: undefined,
domains: [],
id: undefined
id: undefined,
});
return;
}
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === option.key);
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === option.key);
if (!selectedDirectory) {
return;
}

View File

@@ -7,7 +7,7 @@ const createBlankProps = (): DirectoryListProps => {
return {
selectedDirectoryId: undefined,
directories: [],
onNewDirectorySelected: jest.fn()
onNewDirectorySelected: jest.fn(),
};
};
@@ -17,7 +17,7 @@ const createBlankDirectory = (): Tenant => {
displayName: undefined,
domains: [],
id: undefined,
tenantId: undefined
tenantId: undefined,
};
};

View File

@@ -28,7 +28,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
super(props);
this.state = {
filterText: ""
filterText: "",
};
}
@@ -38,12 +38,12 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
const filteredItems =
originalItems && originalItems.length && filterText
? originalItems.filter(
directory =>
(directory) =>
directory.displayName &&
directory.displayName.toLowerCase().indexOf(filterText && filterText.toLowerCase()) >= 0
)
: originalItems;
const filteredItemsSelected = filteredItems.map(t => {
const filteredItemsSelected = filteredItems.map((t) => {
let tenant: ListTenant = t;
tenant.selected = t.tenantId === selectedDirectoryId;
return tenant;
@@ -53,7 +53,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
className: "directoryListFilterTextBox",
placeholder: "Filter by directory name",
onChange: this._onFilterChanged,
ariaLabel: "Directory filter text box"
ariaLabel: "Directory filter text box",
};
// TODO: add magnify glass to search bar with onRenderSuffix
@@ -69,7 +69,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
private _onFilterChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, text?: string): void => {
this.setState({
filterText: text
filterText: text,
});
};
@@ -84,19 +84,19 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
height: "auto",
borderBottom: "1px solid #ccc",
padding: "1px 0",
width: "100%"
width: "100%",
},
rootDisabled: {
backgroundColor: "#f1f1f8"
backgroundColor: "#f1f1f8",
},
rootHovered: {
backgroundColor: "rgba(85,179,255,.1)"
backgroundColor: "rgba(85,179,255,.1)",
},
flexContainer: {
height: "auto",
justifyContent: "flex-start"
}
}
justifyContent: "flex-start",
},
},
};
return (
@@ -115,7 +115,7 @@ export class DirectoryListComponent extends React.Component<DirectoryListProps,
}
const buttonElement = e.currentTarget;
const selectedDirectoryId = buttonElement.getElementsByClassName("directoryListItemId")[0].textContent;
const selectedDirectory = _.find(this.props.directories, d => d.tenantId === selectedDirectoryId);
const selectedDirectory = _.find(this.props.directories, (d) => d.tenantId === selectedDirectoryId);
this.props.onNewDirectorySelected(selectedDirectory);
};

View File

@@ -25,7 +25,7 @@ describe("Dynamic List Component", () => {
placeholder: placeholder,
listItems: items,
buttonText: mockButton,
ariaLabel: mockAriaLabel
ariaLabel: mockAriaLabel,
};
}

View File

@@ -113,5 +113,5 @@ export class DynamicListViewModel extends WaitsForTemplateViewModel {
*/
export const DynamicListComponent = {
viewModel: DynamicListViewModel,
template
template,
};

View File

@@ -1,63 +1,63 @@
import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent";
import template from "./editor-component.html";
import * as monaco from "monaco-editor";
import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service";
/**
* Helper class for ko component registration
*/
export class EditorComponent {
constructor() {
return {
viewModel: EditorViewModel,
template
};
}
}
/**
* Parameters for this component
*/
export interface EditorParams extends JsonEditorParams {
contentType: string;
}
/**
* This is a generic editor component that builds on top of the pre-existing JsonEditorComponent.
*/
// TODO: Ideally, JsonEditorViewModel should extend EditorViewModel and not the other way around
class EditorViewModel extends JsonEditorViewModel {
public params: EditorParams;
private static providerRegistered: string[] = [];
public constructor(params: EditorParams) {
super(params);
this.params = params;
super.createEditor.bind(this);
/**
* setTimeout is needed as creating the edtior manipulates the dom directly and expects
* Knockout to have completed all of the initial bindings for the component
*/
this.params.content() != null &&
setTimeout(() => {
this.createEditor(this.params.content(), this.configureEditor.bind(this));
});
}
protected getEditorLanguage(): string {
return this.params.contentType;
}
protected registerCompletionItemProvider() {
let sqlCompletionItemProvider = new SqlCompletionItemProvider();
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider);
EditorViewModel.providerRegistered.push("sql");
}
}
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return ErrorMarkProvider.getErrorMark(input);
}
}
import { JsonEditorParams, JsonEditorViewModel } from "../JsonEditor/JsonEditorComponent";
import template from "./editor-component.html";
import * as monaco from "monaco-editor";
import { SqlCompletionItemProvider, ErrorMarkProvider } from "@azure/cosmos-language-service";
/**
* Helper class for ko component registration
*/
export class EditorComponent {
constructor() {
return {
viewModel: EditorViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
export interface EditorParams extends JsonEditorParams {
contentType: string;
}
/**
* This is a generic editor component that builds on top of the pre-existing JsonEditorComponent.
*/
// TODO: Ideally, JsonEditorViewModel should extend EditorViewModel and not the other way around
class EditorViewModel extends JsonEditorViewModel {
public params: EditorParams;
private static providerRegistered: string[] = [];
public constructor(params: EditorParams) {
super(params);
this.params = params;
super.createEditor.bind(this);
/**
* setTimeout is needed as creating the edtior manipulates the dom directly and expects
* Knockout to have completed all of the initial bindings for the component
*/
this.params.content() != null &&
setTimeout(() => {
this.createEditor(this.params.content(), this.configureEditor.bind(this));
});
}
protected getEditorLanguage(): string {
return this.params.contentType;
}
protected registerCompletionItemProvider() {
let sqlCompletionItemProvider = new SqlCompletionItemProvider();
if (EditorViewModel.providerRegistered.indexOf("sql") < 0) {
monaco.languages.registerCompletionItemProvider("sql", sqlCompletionItemProvider);
EditorViewModel.providerRegistered.push("sql");
}
}
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return ErrorMarkProvider.getErrorMark(input);
}
}

View File

@@ -70,7 +70,7 @@ export class EditorReact extends React.Component<EditorReactProps> {
fontSize: 12,
ariaLabel: this.props.ariaLabel,
theme: this.props.theme,
automaticLayout: true
automaticLayout: true,
};
this.rootNode.innerHTML = "";

View File

@@ -1,27 +1,27 @@
import template from "./error-display-component.html";
/**
* Helper class for ko component registration
* This component displays an error as designed in:
* https://microsoft.sharepoint.com/teams/DPX/Modern/DocDB/_layouts/15/WopiFrame.aspx?sourcedoc={66864d4a-f925-4cbe-9eb4-79f8d191a115}&action=edit&wd=target%28DocumentDB%20emulator%2Eone%7CE617D0A7-F77C-4968-B75A-1451049F4FEA%2FError%20notification%7CAA1E4BC9-4D72-472C-B40C-2437FA217226%2F%29
* TODO: support "More details"
*/
export class ErrorDisplayComponent {
constructor() {
return {
viewModel: ErrorDisplayViewModel,
template
};
}
}
/**
* Parameters for this component
*/
interface ErrorDisplayParams {
errorMsg: ko.Observable<string>; // Primary message
}
class ErrorDisplayViewModel {
public constructor(public params: ErrorDisplayParams) {}
}
import template from "./error-display-component.html";
/**
* Helper class for ko component registration
* This component displays an error as designed in:
* https://microsoft.sharepoint.com/teams/DPX/Modern/DocDB/_layouts/15/WopiFrame.aspx?sourcedoc={66864d4a-f925-4cbe-9eb4-79f8d191a115}&action=edit&wd=target%28DocumentDB%20emulator%2Eone%7CE617D0A7-F77C-4968-B75A-1451049F4FEA%2FError%20notification%7CAA1E4BC9-4D72-472C-B40C-2437FA217226%2F%29
* TODO: support "More details"
*/
export class ErrorDisplayComponent {
constructor() {
return {
viewModel: ErrorDisplayViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
interface ErrorDisplayParams {
errorMsg: ko.Observable<string>; // Primary message
}
class ErrorDisplayViewModel {
public constructor(public params: ErrorDisplayParams) {}
}

View File

@@ -1,6 +1,6 @@
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent">
<span><img src="/error_red.svg" alt="Error"/></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div>
</div>
<div class="warningErrorContainer" data-bind="visible: !!params.errorMsg()">
<div class="warningErrorContent">
<span><img src="/error_red.svg" alt="Error" /></span>
<span class="settingErrorMsg warningErrorDetailsLinkContainer" data-bind="text: params.errorMsg()"></span>
</div>
</div>

View File

@@ -15,23 +15,23 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
const baseUrlOptions = [
{ key: "https://localhost:1234/explorer.html", text: "localhost:1234" },
{ key: "https://cosmos.azure.com/explorer.html", text: "cosmos.azure.com" },
{ key: "https://portal.azure.com", text: "portal" }
{ key: "https://portal.azure.com", text: "portal" },
];
const platformOptions = [
{ key: "Hosted", text: "Hosted" },
{ key: "Portal", text: "Portal" },
{ key: "Emulator", text: "Emulator" },
{ key: "", text: "None" }
{ key: "", text: "None" },
];
// React hooks to keep state
const [baseUrl, setBaseUrl] = React.useState<IDropdownOption>(
baseUrlOptions.find(o => o.key === window.location.origin + window.location.pathname) || baseUrlOptions[0]
baseUrlOptions.find((o) => o.key === window.location.origin + window.location.pathname) || baseUrlOptions[0]
);
const [platform, setPlatform] = React.useState<IDropdownOption>(
urlParams.has("platform")
? platformOptions.find(o => o.key === urlParams.get("platform")) || platformOptions[0]
? platformOptions.find((o) => o.key === urlParams.get("platform")) || platformOptions[0]
: platformOptions[0]
);
@@ -52,13 +52,13 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{
key: "feature.enableLinkInjection",
label: "Enable Injecting Notebook Viewer Link into the first cell",
value: "true"
value: "true",
},
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{
key: "feature.enablefixedcollectionwithsharedthroughput",
label: "Enable fixed collection with shared throughput",
value: "true"
value: "true",
},
{ key: "feature.ttl90days", label: "TTL 90 days", value: "true" },
{ key: "feature.enablenotebooks", label: "Enable notebooks", value: "true" },
@@ -66,10 +66,10 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
key: "feature.customportal",
label: "Force Production portal (portal only)",
value: "false",
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com"
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com",
},
{ key: "feature.enablespark", label: "Enable Synapse", value: "true" },
{ key: "feature.enableautopilotv2", label: "Enable Auto-pilot V2", value: "true" }
{ key: "feature.enableautopilotv2", label: "Enable Auto-pilot V2", value: "true" },
];
const stringFeatures: {
@@ -88,23 +88,23 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
key: "dataExplorerSource",
label: "Data Explorer Source (portal only)",
placeholder: "https://localhost:1234/explorer.html",
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com"
disabled: (): boolean => baseUrl.key !== "https://portal.azure.com",
},
{ key: "feature.livyendpoint", label: "Livy endpoint", placeholder: "" }
{ key: "feature.livyendpoint", label: "Livy endpoint", placeholder: "" },
];
booleanFeatures.forEach(
f => (f.reactState = React.useState<boolean>(urlParams.has(f.key) ? urlParams.get(f.key) === "true" : false))
(f) => (f.reactState = React.useState<boolean>(urlParams.has(f.key) ? urlParams.get(f.key) === "true" : false))
);
stringFeatures.forEach(
f => (f.reactState = React.useState<string>(urlParams.has(f.key) ? urlParams.get(f.key) : undefined))
(f) => (f.reactState = React.useState<string>(urlParams.has(f.key) ? urlParams.get(f.key) : undefined))
);
const buildUrl = (): string => {
const fragments = (platform.key === "" ? [] : [`platform=${platform.key}`])
.concat(booleanFeatures.map(f => (f.reactState[0] ? `${f.key}=${f.value}` : "")))
.concat(stringFeatures.map(f => (f.reactState[0] ? `${f.key}=${encodeURIComponent(f.reactState[0])}` : "")))
.filter(v => v && v.length > 0);
.concat(booleanFeatures.map((f) => (f.reactState[0] ? `${f.key}=${f.value}` : "")))
.concat(stringFeatures.map((f) => (f.reactState[0] ? `${f.key}=${encodeURIComponent(f.reactState[0])}` : "")))
.filter((v) => v && v.length > 0);
const paramString = fragments.length < 1 ? "" : `?${fragments.join("&")}`;
return `${baseUrl.key}${paramString}`;
@@ -119,38 +119,38 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
};
booleanFeatures.forEach(
f =>
(f) =>
(f.onChange = (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean): void => {
f.reactState[1](checked);
})
);
stringFeatures.forEach(
f =>
(f) =>
(f.onChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
f.reactState[1](newValue);
})
);
const onNotebookShortcut = (): void => {
booleanFeatures.find(f => f.key === "feature.enablenotebooks").reactState[1](true);
booleanFeatures.find((f) => f.key === "feature.enablenotebooks").reactState[1](true);
stringFeatures
.find(f => f.key === "feature.notebookserverurl")
.find((f) => f.key === "feature.notebookserverurl")
.reactState[1]("https://localhost:10001/12345/notebook/");
stringFeatures.find(f => f.key === "feature.notebookservertoken").reactState[1]("token");
stringFeatures.find(f => f.key === "feature.notebookbasepath").reactState[1]("./notebooks");
setPlatform(platformOptions.find(o => o.key === "Hosted"));
stringFeatures.find((f) => f.key === "feature.notebookservertoken").reactState[1]("token");
stringFeatures.find((f) => f.key === "feature.notebookbasepath").reactState[1]("./notebooks");
setPlatform(platformOptions.find((o) => o.key === "Hosted"));
};
const onPortalLocalDEShortcut = (): void => {
setBaseUrl(baseUrlOptions.find(o => o.key === "https://portal.azure.com"));
setPlatform(platformOptions.find(o => o.key === "Portal"));
stringFeatures.find(f => f.key === "dataExplorerSource").reactState[1]("https://localhost:1234/explorer.html");
setBaseUrl(baseUrlOptions.find((o) => o.key === "https://portal.azure.com"));
setPlatform(platformOptions.find((o) => o.key === "Portal"));
stringFeatures.find((f) => f.key === "dataExplorerSource").reactState[1]("https://localhost:1234/explorer.html");
};
const onReset = (): void => {
booleanFeatures.forEach(f => f.reactState[1](false));
stringFeatures.forEach(f => f.reactState[1](""));
booleanFeatures.forEach((f) => f.reactState[1](false));
stringFeatures.forEach((f) => f.reactState[1](""));
};
const stackTokens = { childrenGap: 10 };
@@ -169,7 +169,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
const anchorOptions = {
href: buildUrl(),
target: "_blank",
rel: "noopener"
rel: "noopener",
};
return (
@@ -201,7 +201,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
</Stack>
<Stack horizontal>
<Stack className="checkboxRow" horizontalAlign="space-between">
{leftBooleanFeatures.map(f => (
{leftBooleanFeatures.map((f) => (
<Checkbox
key={f.key}
label={f.label}
@@ -212,7 +212,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
))}
</Stack>
<Stack className="checkboxRow" horizontalAlign="space-between">
{rightBooleanFeatures.map(f => (
{rightBooleanFeatures.map((f) => (
<Checkbox
key={f.key}
label={f.label}
@@ -225,7 +225,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
</Stack>
<Stack horizontal tokens={stackTokens}>
<Stack horizontalAlign="space-between">
{leftStringFeatures.map(f => (
{leftStringFeatures.map((f) => (
<TextField
key={f.key}
value={f.reactState[0]}
@@ -238,7 +238,7 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
))}
</Stack>
<Stack horizontalAlign="space-between">
{rightStringFeatures.map(f => (
{rightStringFeatures.map((f) => (
<TextField
key={f.key}
value={f.reactState[0]}

View File

@@ -20,7 +20,7 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
container: {
display: "flex",
flexFlow: "column nowrap",
alignItems: "stretch"
alignItems: "stretch",
},
header: [
// tslint:disable-next-line:deprecation
@@ -32,16 +32,16 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
display: "flex",
alignItems: "center",
fontWeight: FontWeights.semibold,
padding: "12px 12px 14px 24px"
}
padding: "12px 12px 14px 24px",
},
],
body: {
flex: "4 4 auto",
overflowY: "hidden",
marginBottom: 40,
height: "100%",
display: "flex"
}
display: "flex",
},
});
const iconButtonStyles = {
@@ -49,11 +49,11 @@ export const FeaturePanelLauncher: React.FunctionComponent = (): JSX.Element =>
color: theme.palette.neutralPrimary,
marginLeft: "auto",
marginTop: "4px",
marginRight: "2px"
marginRight: "2px",
},
rootHovered: {
color: theme.palette.neutralDark
}
color: theme.palette.neutralDark,
},
};
const cancelIcon: IIconProps = { iconName: "Cancel" };
const hideModal = (): void => showModal(false);

View File

@@ -1,132 +1,132 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent";
import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
container: Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void;
}
interface AddRepoComponentState {
textFieldValue: string;
textFieldErrorMessage: string;
}
export class AddRepoComponent extends React.Component<AddRepoComponentProps, AddRepoComponentState> {
private static readonly DescriptionText =
"Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: ";
private static readonly ButtonText = "Add";
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
private static readonly TextFieldErrorMessage = "Invalid url";
private static readonly DefaultBranchName = "master";
constructor(props: AddRepoComponentProps) {
super(props);
this.state = {
textFieldValue: "",
textFieldErrorMessage: undefined
};
}
public render(): JSX.Element {
const textFieldProps: ITextFieldProps = {
placeholder: AddRepoComponent.TextFieldPlaceholder,
autoFocus: true,
value: this.state.textFieldValue,
errorMessage: this.state.textFieldErrorMessage,
onChange: this.onTextFieldChange
};
const buttonProps: IButtonProps = {
text: AddRepoComponent.ButtonText,
ariaLabel: AddRepoComponent.ButtonText,
onClick: this.onAddRepoButtonClick
};
return (
<>
<p style={{ marginBottom: ChildrenMargin }}>{AddRepoComponent.DescriptionText}</p>
<TextField {...textFieldProps} />
<DefaultButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</>
);
}
private onTextFieldChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
this.setState({
textFieldValue: newValue || "",
textFieldErrorMessage: undefined
});
};
private onAddRepoButtonClick = async (): Promise<void> => {
const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook
});
let enteredUrl = this.state.textFieldValue;
if (enteredUrl.indexOf("/tree/") === -1) {
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
}
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
if (repoInfo) {
this.setState({
textFieldValue: "",
textFieldErrorMessage: undefined
});
const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo);
if (repo) {
const item: RepoListItem = {
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
repo,
branches: [
{
name: repoInfo.branch
}
]
};
TelemetryProcessor.traceSuccess(
Action.NotebooksGitHubManualRepoAdd,
{
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook
},
startKey
);
return this.props.pinRepo(item);
}
}
this.setState({
textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage
});
TelemetryProcessor.traceFailure(
Action.NotebooksGitHubManualRepoAdd,
{
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
error: AddRepoComponent.TextFieldErrorMessage
},
startKey
);
};
}
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent";
import { ChildrenMargin } from "./GitHubStyleConstants";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
container: Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void;
}
interface AddRepoComponentState {
textFieldValue: string;
textFieldErrorMessage: string;
}
export class AddRepoComponent extends React.Component<AddRepoComponentProps, AddRepoComponentState> {
private static readonly DescriptionText =
"Don't see what you're looking for? Add your repo/branch, or any public repo (read-access only) by entering the URL: ";
private static readonly ButtonText = "Add";
private static readonly TextFieldPlaceholder = "https://github.com/owner/repo/tree/branch";
private static readonly TextFieldErrorMessage = "Invalid url";
private static readonly DefaultBranchName = "master";
constructor(props: AddRepoComponentProps) {
super(props);
this.state = {
textFieldValue: "",
textFieldErrorMessage: undefined,
};
}
public render(): JSX.Element {
const textFieldProps: ITextFieldProps = {
placeholder: AddRepoComponent.TextFieldPlaceholder,
autoFocus: true,
value: this.state.textFieldValue,
errorMessage: this.state.textFieldErrorMessage,
onChange: this.onTextFieldChange,
};
const buttonProps: IButtonProps = {
text: AddRepoComponent.ButtonText,
ariaLabel: AddRepoComponent.ButtonText,
onClick: this.onAddRepoButtonClick,
};
return (
<>
<p style={{ marginBottom: ChildrenMargin }}>{AddRepoComponent.DescriptionText}</p>
<TextField {...textFieldProps} />
<DefaultButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</>
);
}
private onTextFieldChange = (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
newValue?: string
): void => {
this.setState({
textFieldValue: newValue || "",
textFieldErrorMessage: undefined,
});
};
private onAddRepoButtonClick = async (): Promise<void> => {
const startKey: number = TelemetryProcessor.traceStart(Action.NotebooksGitHubManualRepoAdd, {
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
});
let enteredUrl = this.state.textFieldValue;
if (enteredUrl.indexOf("/tree/") === -1) {
enteredUrl = UrlUtility.createUri(enteredUrl, `tree/${AddRepoComponent.DefaultBranchName}`);
}
const repoInfo = GitHubUtils.fromRepoUri(enteredUrl);
if (repoInfo) {
this.setState({
textFieldValue: "",
textFieldErrorMessage: undefined,
});
const repo = await this.props.getRepo(repoInfo.owner, repoInfo.repo);
if (repo) {
const item: RepoListItem = {
key: GitHubUtils.toRepoFullName(repo.owner, repo.name),
repo,
branches: [
{
name: repoInfo.branch,
},
],
};
TelemetryProcessor.traceSuccess(
Action.NotebooksGitHubManualRepoAdd,
{
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
},
startKey
);
return this.props.pinRepo(item);
}
}
this.setState({
textFieldErrorMessage: AddRepoComponent.TextFieldErrorMessage,
});
TelemetryProcessor.traceFailure(
Action.NotebooksGitHubManualRepoAdd,
{
databaseAccountName: this.props.container.databaseAccount() && this.props.container.databaseAccount().name,
defaultExperience: this.props.container.defaultExperience && this.props.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
error: AddRepoComponent.TextFieldErrorMessage,
},
startKey
);
};
}

View File

@@ -1,91 +1,91 @@
import {
ChoiceGroup,
IButtonProps,
IChoiceGroupProps,
PrimaryButton,
IChoiceGroupOption
} from "office-ui-fabric-react";
import * as React from "react";
import { ChildrenMargin } from "./GitHubStyleConstants";
export interface AuthorizeAccessComponentProps {
scope: string;
authorizeAccess: (scope: string) => void;
}
export interface AuthorizeAccessComponentState {
scope: string;
}
export class AuthorizeAccessComponent extends React.Component<
AuthorizeAccessComponentProps,
AuthorizeAccessComponentState
> {
// Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos.
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
public static readonly Scopes = {
Public: {
key: "public_repo",
text: "Public repos only"
},
PublicAndPrivate: {
key: "repo",
text: "Public and private repos"
}
};
private static readonly DescriptionPara1 =
"Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer.";
private static readonly DescriptionPara2 =
"Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: ";
private static readonly AuthorizeButtonText = "Authorize access";
private onChoiceGroupChange = (event: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void =>
this.setState({
scope: option.key
});
private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope);
constructor(props: AuthorizeAccessComponentProps) {
super(props);
this.state = {
scope: this.props.scope
};
}
public render(): JSX.Element {
const choiceGroupProps: IChoiceGroupProps = {
options: [
{
key: AuthorizeAccessComponent.Scopes.Public.key,
text: AuthorizeAccessComponent.Scopes.Public.text,
ariaLabel: AuthorizeAccessComponent.Scopes.Public.text
},
{
key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key,
text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text
}
],
selectedKey: this.state.scope,
onChange: this.onChoiceGroupChange
};
const buttonProps: IButtonProps = {
text: AuthorizeAccessComponent.AuthorizeButtonText,
ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText,
onClick: this.onButtonClick
};
return (
<>
<p>{AuthorizeAccessComponent.DescriptionPara1}</p>
<p style={{ marginTop: ChildrenMargin }}>{AuthorizeAccessComponent.DescriptionPara2}</p>
<ChoiceGroup style={{ marginTop: ChildrenMargin }} {...choiceGroupProps} />
<PrimaryButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</>
);
}
}
import {
ChoiceGroup,
IButtonProps,
IChoiceGroupProps,
PrimaryButton,
IChoiceGroupOption,
} from "office-ui-fabric-react";
import * as React from "react";
import { ChildrenMargin } from "./GitHubStyleConstants";
export interface AuthorizeAccessComponentProps {
scope: string;
authorizeAccess: (scope: string) => void;
}
export interface AuthorizeAccessComponentState {
scope: string;
}
export class AuthorizeAccessComponent extends React.Component<
AuthorizeAccessComponentProps,
AuthorizeAccessComponentState
> {
// Scopes supported by GitHub OAuth. We're only interested in ones which allow us access to repos.
// https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/
public static readonly Scopes = {
Public: {
key: "public_repo",
text: "Public repos only",
},
PublicAndPrivate: {
key: "repo",
text: "Public and private repos",
},
};
private static readonly DescriptionPara1 =
"Connect your notebooks workspace to GitHub. You'll be able to view, edit, and run notebooks stored in your GitHub repositories in Data Explorer.";
private static readonly DescriptionPara2 =
"Complete setup by authorizing Azure Cosmos DB to access the repositories in your GitHub account: ";
private static readonly AuthorizeButtonText = "Authorize access";
private onChoiceGroupChange = (event: React.SyntheticEvent<HTMLElement>, option: IChoiceGroupOption): void =>
this.setState({
scope: option.key,
});
private onButtonClick = (): void => this.props.authorizeAccess(this.state.scope);
constructor(props: AuthorizeAccessComponentProps) {
super(props);
this.state = {
scope: this.props.scope,
};
}
public render(): JSX.Element {
const choiceGroupProps: IChoiceGroupProps = {
options: [
{
key: AuthorizeAccessComponent.Scopes.Public.key,
text: AuthorizeAccessComponent.Scopes.Public.text,
ariaLabel: AuthorizeAccessComponent.Scopes.Public.text,
},
{
key: AuthorizeAccessComponent.Scopes.PublicAndPrivate.key,
text: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
ariaLabel: AuthorizeAccessComponent.Scopes.PublicAndPrivate.text,
},
],
selectedKey: this.state.scope,
onChange: this.onChoiceGroupChange,
};
const buttonProps: IButtonProps = {
text: AuthorizeAccessComponent.AuthorizeButtonText,
ariaLabel: AuthorizeAccessComponent.AuthorizeButtonText,
onClick: this.onButtonClick,
};
return (
<>
<p>{AuthorizeAccessComponent.DescriptionPara1}</p>
<p style={{ marginTop: ChildrenMargin }}>{AuthorizeAccessComponent.DescriptionPara2}</p>
<ChoiceGroup style={{ marginTop: ChildrenMargin }} {...choiceGroupProps} />
<PrimaryButton style={{ marginTop: ChildrenMargin }} {...buttonProps} />
</>
);
}
}

View File

@@ -1,87 +1,87 @@
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react";
import * as React from "react";
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants";
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
export interface GitHubReposComponentProps {
showAuthorizeAccess: boolean;
authorizeAccessProps: AuthorizeAccessComponentProps;
reposListProps: ReposListComponentProps;
addRepoProps: AddRepoComponentProps;
resetConnection: () => void;
onOkClick: () => void;
onCancelClick: () => void;
}
export interface RepoListItem {
key: string;
repo: IGitHubRepo;
branches: IGitHubBranch[];
}
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
private static readonly OKButtonText = "OK";
private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element {
const header: JSX.Element = (
<p>
{this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle}
</p>
);
const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : (
<>
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
<Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}>
{GitHubReposComponent.ManageGitHubRepoResetConnection}
</Link>
<ReposListComponent {...this.props.reposListProps} />
</>
);
const okProps: IButtonProps = {
text: GitHubReposComponent.OKButtonText,
ariaLabel: GitHubReposComponent.OKButtonText,
onClick: this.props.onOkClick
};
const cancelProps: IButtonProps = {
text: GitHubReposComponent.CancelButtonText,
ariaLabel: GitHubReposComponent.CancelButtonText,
onClick: this.props.onCancelClick
};
return (
<>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && (
<>
<div className={"paneFooter"} style={ContentFooterStyle}>
<AddRepoComponent {...this.props.addRepoProps} />
</div>
<div className={"paneFooter"} style={ButtonsFooterStyle}>
<PrimaryButton {...okProps} />
<DefaultButton style={{ marginLeft: ChildrenMargin }} {...cancelProps} />
</div>
</>
)}
</>
);
}
}
import { DefaultButton, IButtonProps, Link, PrimaryButton } from "office-ui-fabric-react";
import * as React from "react";
import { IGitHubBranch, IGitHubRepo } from "../../../GitHub/GitHubClient";
import { AddRepoComponent, AddRepoComponentProps } from "./AddRepoComponent";
import { AuthorizeAccessComponent, AuthorizeAccessComponentProps } from "./AuthorizeAccessComponent";
import { ButtonsFooterStyle, ChildrenMargin, ContentFooterStyle } from "./GitHubStyleConstants";
import { ReposListComponent, ReposListComponentProps } from "./ReposListComponent";
export interface GitHubReposComponentProps {
showAuthorizeAccess: boolean;
authorizeAccessProps: AuthorizeAccessComponentProps;
reposListProps: ReposListComponentProps;
addRepoProps: AddRepoComponentProps;
resetConnection: () => void;
onOkClick: () => void;
onCancelClick: () => void;
}
export interface RepoListItem {
key: string;
repo: IGitHubRepo;
branches: IGitHubBranch[];
}
export class GitHubReposComponent extends React.Component<GitHubReposComponentProps> {
public static readonly ConnectToGitHubTitle = "Connect to GitHub";
public static readonly ManageGitHubRepoTitle = "Manage GitHub settings";
private static readonly ManageGitHubRepoDescription =
"Select your GitHub repos and branch(es) to pin to your notebooks workspace.";
private static readonly ManageGitHubRepoResetConnection = "View or change your GitHub authorization settings.";
private static readonly OKButtonText = "OK";
private static readonly CancelButtonText = "Cancel";
public render(): JSX.Element {
const header: JSX.Element = (
<p>
{this.props.showAuthorizeAccess
? GitHubReposComponent.ConnectToGitHubTitle
: GitHubReposComponent.ManageGitHubRepoTitle}
</p>
);
const content: JSX.Element = this.props.showAuthorizeAccess ? (
<AuthorizeAccessComponent {...this.props.authorizeAccessProps} />
) : (
<>
<p>{GitHubReposComponent.ManageGitHubRepoDescription}</p>
<Link style={{ marginTop: ChildrenMargin }} onClick={this.props.resetConnection}>
{GitHubReposComponent.ManageGitHubRepoResetConnection}
</Link>
<ReposListComponent {...this.props.reposListProps} />
</>
);
const okProps: IButtonProps = {
text: GitHubReposComponent.OKButtonText,
ariaLabel: GitHubReposComponent.OKButtonText,
onClick: this.props.onOkClick,
};
const cancelProps: IButtonProps = {
text: GitHubReposComponent.CancelButtonText,
ariaLabel: GitHubReposComponent.CancelButtonText,
onClick: this.props.onCancelClick,
};
return (
<>
<div className={"firstdivbg headerline"} role="heading" aria-level={2}>
{header}
</div>
<div className={"paneMainContent"}>{content}</div>
{!this.props.showAuthorizeAccess && (
<>
<div className={"paneFooter"} style={ContentFooterStyle}>
<AddRepoComponent {...this.props.addRepoProps} />
</div>
<div className={"paneFooter"} style={ButtonsFooterStyle}>
<PrimaryButton {...okProps} />
<DefaultButton style={{ marginLeft: ChildrenMargin }} {...cancelProps} />
</div>
</>
)}
</>
);
}
}

View File

@@ -1,20 +1,20 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
export class GitHubReposComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GitHubReposComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GitHubReposComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { GitHubReposComponent, GitHubReposComponentProps } from "./GitHubReposComponent";
export class GitHubReposComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GitHubReposComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GitHubReposComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -1,58 +1,58 @@
import {
IStyleFunctionOrObject,
ICheckboxStyleProps,
ICheckboxStyles,
IDropdownStyles,
IDropdownStyleProps
} from "office-ui-fabric-react";
export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14,
height: "auto"
};
export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px",
height: "auto"
};
export const ChildrenMargin = 10;
export const FontSize = 12;
export const ReposListCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: {
margin: 0,
padding: "2 0 2 0"
},
text: {
fontSize: FontSize
}
};
export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: {
margin: 0,
padding: 0,
fontSize: FontSize
},
root: {
padding: 0
},
text: {
fontSize: FontSize
}
};
export const BranchesDropdownStyles: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles> = {
title: {
fontSize: FontSize
}
};
export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8
};
export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200;
import {
IStyleFunctionOrObject,
ICheckboxStyleProps,
ICheckboxStyles,
IDropdownStyles,
IDropdownStyleProps,
} from "office-ui-fabric-react";
export const ButtonsFooterStyle: React.CSSProperties = {
padding: 14,
height: "auto",
};
export const ContentFooterStyle: React.CSSProperties = {
padding: "10px 24px 10px 24px",
height: "auto",
};
export const ChildrenMargin = 10;
export const FontSize = 12;
export const ReposListCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: {
margin: 0,
padding: "2 0 2 0",
},
text: {
fontSize: FontSize,
},
};
export const BranchesDropdownCheckboxStyles: IStyleFunctionOrObject<ICheckboxStyleProps, ICheckboxStyles> = {
label: {
margin: 0,
padding: 0,
fontSize: FontSize,
},
root: {
padding: 0,
},
text: {
fontSize: FontSize,
},
};
export const BranchesDropdownStyles: IStyleFunctionOrObject<IDropdownStyleProps, IDropdownStyles> = {
title: {
fontSize: FontSize,
},
};
export const BranchesDropdownOptionContainerStyle: React.CSSProperties = {
padding: 8,
};
export const ReposListRepoColumnMinWidth = 192;
export const ReposListBranchesColumnWidth = 116;
export const BranchesDropdownWidth = 200;

View File

@@ -1,304 +1,304 @@
import {
Checkbox,
DetailsList,
DetailsRow,
Dropdown,
ICheckboxProps,
IDetailsFooterProps,
IDetailsListProps,
IDetailsRowBaseProps,
IDropdown,
IDropdownOption,
IDropdownProps,
ILinkProps,
ISelectableDroppableTextProps,
Link,
ResponsiveMode,
SelectionMode,
Text
} from "office-ui-fabric-react";
import * as React from "react";
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { RepoListItem } from "./GitHubReposComponent";
import {
BranchesDropdownCheckboxStyles,
BranchesDropdownOptionContainerStyle,
ReposListCheckboxStyles,
ReposListRepoColumnMinWidth,
ReposListBranchesColumnWidth,
BranchesDropdownWidth,
BranchesDropdownStyles
} from "./GitHubStyleConstants";
export interface ReposListComponentProps {
branchesProps: Record<string, BranchesProps>; // key'd by repo key
pinnedReposProps: PinnedReposProps;
unpinnedReposProps: UnpinnedReposProps;
pinRepo: (repo: RepoListItem) => void;
unpinRepo: (repo: RepoListItem) => void;
}
export interface BranchesProps {
branches: IGitHubBranch[];
lastPageInfo?: IGitHubPageInfo;
hasMore: boolean;
isLoading: boolean;
loadMore: () => void;
}
export interface PinnedReposProps {
repos: RepoListItem[];
}
export interface UnpinnedReposProps {
repos: RepoListItem[];
hasMore: boolean;
isLoading: boolean;
loadMore: () => void;
}
export class ReposListComponent extends React.Component<ReposListComponentProps> {
private static readonly PinnedReposColumnName = "Pinned repos";
private static readonly UnpinnedReposColumnName = "Unpinned repos";
private static readonly BranchesColumnName = "Branches";
private static readonly LoadingText = "Loading...";
private static readonly LoadMoreText = "Load more";
private static readonly DefaultBranchName = "master";
private static readonly FooterIndex = -1;
public render(): JSX.Element {
const pinnedReposListProps: IDetailsListProps = {
styles: {
contentWrapper: {
height: this.props.pinnedReposProps.repos.length ? undefined : 0
}
},
items: this.props.pinnedReposProps.repos,
getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none,
compact: true,
columns: [
{
key: ReposListComponent.PinnedReposColumnName,
name: ReposListComponent.PinnedReposColumnName,
ariaLabel: ReposListComponent.PinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderPinnedReposColumnItem
},
{
key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderPinnedReposBranchesColumnItem
}
],
onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter
};
const unpinnedReposListProps: IDetailsListProps = {
items: this.props.unpinnedReposProps.repos,
getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none,
compact: true,
columns: [
{
key: ReposListComponent.UnpinnedReposColumnName,
name: ReposListComponent.UnpinnedReposColumnName,
ariaLabel: ReposListComponent.UnpinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderUnpinnedReposColumnItem
},
{
key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderUnpinnedReposBranchesColumnItem
}
],
onRenderDetailsFooter:
this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore
? this.onRenderReposFooter
: undefined
};
return (
<>
<DetailsList {...pinnedReposListProps} />
<DetailsList {...unpinnedReposListProps} />
</>
);
}
private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <Text>None</Text>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles,
defaultChecked: true,
onChange: () => this.props.unpinRepo(item)
};
return <Checkbox {...checkboxProps} />;
};
private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <></>;
}
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
const options: IDropdownOption[] = branchesProps.branches.map(branch => ({
key: branch.name,
text: branch.name,
data: item,
disabled: item.branches.length === 1 && branch.name === item.branches[0].name,
selected: item.branches.findIndex(element => element.name === branch.name) !== -1
}));
if (branchesProps.hasMore || branchesProps.isLoading) {
const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText;
options.push({
key: text,
text,
data: item,
index: ReposListComponent.FooterIndex
});
}
const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles,
dropdownWidth: BranchesDropdownWidth,
responsiveMode: ResponsiveMode.large,
options,
onRenderList: this.onRenderBranchesDropdownList
};
if (item.branches.length === 1) {
dropdownProps.placeholder = item.branches[0].name;
} else if (item.branches.length > 1) {
dropdownProps.placeholder = `${item.branches.length} branches`;
}
return <Dropdown {...dropdownProps} />;
};
private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <></>;
}
const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles,
options: [],
placeholder: ReposListComponent.DefaultBranchName,
disabled: true
};
return <Dropdown {...dropdownProps} />;
};
private onRenderBranchesDropdownList = (
props: ISelectableDroppableTextProps<IDropdown, HTMLDivElement>
): JSX.Element => {
const renderedList: JSX.Element[] = [];
props.options.forEach((option: IDropdownOption) => {
const item = (
<div key={option.key} style={BranchesDropdownOptionContainerStyle}>
{this.onRenderPinnedReposBranchesDropdownOption(option)}
</div>
);
renderedList.push(item);
});
return <>{renderedList}</>;
};
private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element {
const item: RepoListItem = option.data;
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
if (option.index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = {
disabled: branchesProps.isLoading,
onClick: branchesProps.loadMore
};
return <Link {...linkProps}>{option.text}</Link>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(option.text),
styles: BranchesDropdownCheckboxStyles,
defaultChecked: option.selected,
disabled: option.disabled,
onChange: (event, checked) => {
const repoListItem = { ...item };
const branch: IGitHubBranch = { name: option.text };
repoListItem.branches = repoListItem.branches.filter(element => element.name !== branch.name);
if (checked) {
repoListItem.branches.push(branch);
}
this.props.pinRepo(repoListItem);
}
};
return <Checkbox {...checkboxProps} />;
}
private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = {
disabled: this.props.unpinnedReposProps.isLoading,
onClick: this.props.unpinnedReposProps.loadMore
};
const linkText = this.props.unpinnedReposProps.isLoading
? ReposListComponent.LoadingText
: ReposListComponent.LoadMoreText;
return <Link {...linkProps}>{linkText}</Link>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles,
onChange: () => {
const repoListItem = { ...item };
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
this.props.pinRepo(repoListItem);
}
};
return <Checkbox {...checkboxProps} />;
};
private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => {
const props: IDetailsRowBaseProps = {
...detailsFooterProps,
item: {},
itemIndex: ReposListComponent.FooterIndex
};
return <DetailsRow {...props} />;
};
private static getCheckboxPropsForLabel(label: string): ICheckboxProps {
return {
label,
title: label,
ariaLabel: label
};
}
private static getKey(item: RepoListItem): string {
return item.key;
}
}
import {
Checkbox,
DetailsList,
DetailsRow,
Dropdown,
ICheckboxProps,
IDetailsFooterProps,
IDetailsListProps,
IDetailsRowBaseProps,
IDropdown,
IDropdownOption,
IDropdownProps,
ILinkProps,
ISelectableDroppableTextProps,
Link,
ResponsiveMode,
SelectionMode,
Text,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGitHubBranch, IGitHubPageInfo } from "../../../GitHub/GitHubClient";
import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { RepoListItem } from "./GitHubReposComponent";
import {
BranchesDropdownCheckboxStyles,
BranchesDropdownOptionContainerStyle,
ReposListCheckboxStyles,
ReposListRepoColumnMinWidth,
ReposListBranchesColumnWidth,
BranchesDropdownWidth,
BranchesDropdownStyles,
} from "./GitHubStyleConstants";
export interface ReposListComponentProps {
branchesProps: Record<string, BranchesProps>; // key'd by repo key
pinnedReposProps: PinnedReposProps;
unpinnedReposProps: UnpinnedReposProps;
pinRepo: (repo: RepoListItem) => void;
unpinRepo: (repo: RepoListItem) => void;
}
export interface BranchesProps {
branches: IGitHubBranch[];
lastPageInfo?: IGitHubPageInfo;
hasMore: boolean;
isLoading: boolean;
loadMore: () => void;
}
export interface PinnedReposProps {
repos: RepoListItem[];
}
export interface UnpinnedReposProps {
repos: RepoListItem[];
hasMore: boolean;
isLoading: boolean;
loadMore: () => void;
}
export class ReposListComponent extends React.Component<ReposListComponentProps> {
private static readonly PinnedReposColumnName = "Pinned repos";
private static readonly UnpinnedReposColumnName = "Unpinned repos";
private static readonly BranchesColumnName = "Branches";
private static readonly LoadingText = "Loading...";
private static readonly LoadMoreText = "Load more";
private static readonly DefaultBranchName = "master";
private static readonly FooterIndex = -1;
public render(): JSX.Element {
const pinnedReposListProps: IDetailsListProps = {
styles: {
contentWrapper: {
height: this.props.pinnedReposProps.repos.length ? undefined : 0,
},
},
items: this.props.pinnedReposProps.repos,
getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none,
compact: true,
columns: [
{
key: ReposListComponent.PinnedReposColumnName,
name: ReposListComponent.PinnedReposColumnName,
ariaLabel: ReposListComponent.PinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderPinnedReposColumnItem,
},
{
key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderPinnedReposBranchesColumnItem,
},
],
onRenderDetailsFooter: this.props.pinnedReposProps.repos.length ? undefined : this.onRenderReposFooter,
};
const unpinnedReposListProps: IDetailsListProps = {
items: this.props.unpinnedReposProps.repos,
getKey: ReposListComponent.getKey,
selectionMode: SelectionMode.none,
compact: true,
columns: [
{
key: ReposListComponent.UnpinnedReposColumnName,
name: ReposListComponent.UnpinnedReposColumnName,
ariaLabel: ReposListComponent.UnpinnedReposColumnName,
minWidth: ReposListRepoColumnMinWidth,
onRender: this.onRenderUnpinnedReposColumnItem,
},
{
key: ReposListComponent.BranchesColumnName,
name: ReposListComponent.BranchesColumnName,
ariaLabel: ReposListComponent.BranchesColumnName,
minWidth: ReposListBranchesColumnWidth,
maxWidth: ReposListBranchesColumnWidth,
onRender: this.onRenderUnpinnedReposBranchesColumnItem,
},
],
onRenderDetailsFooter:
this.props.unpinnedReposProps.isLoading || this.props.unpinnedReposProps.hasMore
? this.onRenderReposFooter
: undefined,
};
return (
<>
<DetailsList {...pinnedReposListProps} />
<DetailsList {...unpinnedReposListProps} />
</>
);
}
private onRenderPinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <Text>None</Text>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles,
defaultChecked: true,
onChange: () => this.props.unpinRepo(item),
};
return <Checkbox {...checkboxProps} />;
};
private onRenderPinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <></>;
}
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
const options: IDropdownOption[] = branchesProps.branches.map((branch) => ({
key: branch.name,
text: branch.name,
data: item,
disabled: item.branches.length === 1 && branch.name === item.branches[0].name,
selected: item.branches.findIndex((element) => element.name === branch.name) !== -1,
}));
if (branchesProps.hasMore || branchesProps.isLoading) {
const text = branchesProps.isLoading ? ReposListComponent.LoadingText : ReposListComponent.LoadMoreText;
options.push({
key: text,
text,
data: item,
index: ReposListComponent.FooterIndex,
});
}
const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles,
dropdownWidth: BranchesDropdownWidth,
responsiveMode: ResponsiveMode.large,
options,
onRenderList: this.onRenderBranchesDropdownList,
};
if (item.branches.length === 1) {
dropdownProps.placeholder = item.branches[0].name;
} else if (item.branches.length > 1) {
dropdownProps.placeholder = `${item.branches.length} branches`;
}
return <Dropdown {...dropdownProps} />;
};
private onRenderUnpinnedReposBranchesColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
return <></>;
}
const dropdownProps: IDropdownProps = {
styles: BranchesDropdownStyles,
options: [],
placeholder: ReposListComponent.DefaultBranchName,
disabled: true,
};
return <Dropdown {...dropdownProps} />;
};
private onRenderBranchesDropdownList = (
props: ISelectableDroppableTextProps<IDropdown, HTMLDivElement>
): JSX.Element => {
const renderedList: JSX.Element[] = [];
props.options.forEach((option: IDropdownOption) => {
const item = (
<div key={option.key} style={BranchesDropdownOptionContainerStyle}>
{this.onRenderPinnedReposBranchesDropdownOption(option)}
</div>
);
renderedList.push(item);
});
return <>{renderedList}</>;
};
private onRenderPinnedReposBranchesDropdownOption(option: IDropdownOption): JSX.Element {
const item: RepoListItem = option.data;
const branchesProps = this.props.branchesProps[GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)];
if (option.index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = {
disabled: branchesProps.isLoading,
onClick: branchesProps.loadMore,
};
return <Link {...linkProps}>{option.text}</Link>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(option.text),
styles: BranchesDropdownCheckboxStyles,
defaultChecked: option.selected,
disabled: option.disabled,
onChange: (event, checked) => {
const repoListItem = { ...item };
const branch: IGitHubBranch = { name: option.text };
repoListItem.branches = repoListItem.branches.filter((element) => element.name !== branch.name);
if (checked) {
repoListItem.branches.push(branch);
}
this.props.pinRepo(repoListItem);
},
};
return <Checkbox {...checkboxProps} />;
}
private onRenderUnpinnedReposColumnItem = (item: RepoListItem, index: number): JSX.Element => {
if (index === ReposListComponent.FooterIndex) {
const linkProps: ILinkProps = {
disabled: this.props.unpinnedReposProps.isLoading,
onClick: this.props.unpinnedReposProps.loadMore,
};
const linkText = this.props.unpinnedReposProps.isLoading
? ReposListComponent.LoadingText
: ReposListComponent.LoadMoreText;
return <Link {...linkProps}>{linkText}</Link>;
}
const checkboxProps: ICheckboxProps = {
...ReposListComponent.getCheckboxPropsForLabel(GitHubUtils.toRepoFullName(item.repo.owner, item.repo.name)),
styles: ReposListCheckboxStyles,
onChange: () => {
const repoListItem = { ...item };
repoListItem.branches = [{ name: ReposListComponent.DefaultBranchName }];
this.props.pinRepo(repoListItem);
},
};
return <Checkbox {...checkboxProps} />;
};
private onRenderReposFooter = (detailsFooterProps: IDetailsFooterProps): JSX.Element => {
const props: IDetailsRowBaseProps = {
...detailsFooterProps,
item: {},
itemIndex: ReposListComponent.FooterIndex,
};
return <DetailsRow {...props} />;
};
private static getCheckboxPropsForLabel(label: string): ICheckboxProps {
return {
label,
title: label,
ariaLabel: label,
};
}
private static getKey(item: RepoListItem): string {
return item.key;
}
}

View File

@@ -1,81 +1,81 @@
import * as React from "react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "office-ui-fabric-react";
export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure";
private static readonly cosmosdbText = "Cosmos DB";
private static readonly galleryText = "Gallery";
private static readonly loginText = "Sign In";
private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank");
private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href);
private static readonly headerItemStyle: React.CSSProperties = {
color: "white"
};
private static readonly mainHeaderTextProps: ITextProps = {
style: GalleryHeaderComponent.headerItemStyle,
variant: "mediumPlus",
styles: {
root: {
fontWeight: FontWeights.semibold
}
}
};
private static readonly headerItemTextProps: ITextProps = { style: GalleryHeaderComponent.headerItemStyle };
private renderHeaderItem = (text: string, onClick: () => void, textProps: ITextProps): JSX.Element => {
return (
<CommandButton onClick={onClick} ariaLabel={text}>
<Text {...textProps}>{text}</Text>
</CommandButton>
);
};
public render(): JSX.Element {
return (
<Stack
tokens={{ childrenGap: 10 }}
horizontal
styles={{ root: { background: "#0078d4", paddingLeft: 20, paddingRight: 20 } }}
verticalAlign="center"
>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.azureText,
GalleryHeaderComponent.openPortal,
GalleryHeaderComponent.mainHeaderTextProps
)}
</Stack.Item>
<Stack.Item>
<Separator vertical />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.cosmosdbText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item>
<FontIcon style={GalleryHeaderComponent.headerItemStyle} iconName="ChevronRight" />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.galleryText,
undefined,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item grow>
<></>
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.loginText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
</Stack>
);
}
}
import * as React from "react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "office-ui-fabric-react";
export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure";
private static readonly cosmosdbText = "Cosmos DB";
private static readonly galleryText = "Gallery";
private static readonly loginText = "Sign In";
private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank");
private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href);
private static readonly headerItemStyle: React.CSSProperties = {
color: "white",
};
private static readonly mainHeaderTextProps: ITextProps = {
style: GalleryHeaderComponent.headerItemStyle,
variant: "mediumPlus",
styles: {
root: {
fontWeight: FontWeights.semibold,
},
},
};
private static readonly headerItemTextProps: ITextProps = { style: GalleryHeaderComponent.headerItemStyle };
private renderHeaderItem = (text: string, onClick: () => void, textProps: ITextProps): JSX.Element => {
return (
<CommandButton onClick={onClick} ariaLabel={text}>
<Text {...textProps}>{text}</Text>
</CommandButton>
);
};
public render(): JSX.Element {
return (
<Stack
tokens={{ childrenGap: 10 }}
horizontal
styles={{ root: { background: "#0078d4", paddingLeft: 20, paddingRight: 20 } }}
verticalAlign="center"
>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.azureText,
GalleryHeaderComponent.openPortal,
GalleryHeaderComponent.mainHeaderTextProps
)}
</Stack.Item>
<Stack.Item>
<Separator vertical />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.cosmosdbText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item>
<FontIcon style={GalleryHeaderComponent.headerItemStyle} iconName="ChevronRight" />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.galleryText,
undefined,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item grow>
<></>
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.loginText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
</Stack>
);
}
}

View File

@@ -1,186 +1,186 @@
/**
* How to use this component:
*
* In your html markup, use:
* <input-typeahead params="{
choices:choices,
selection:selection,
inputValue:inputValue,
placeholder:'Enter source',
typeaheadOverrideOptions:typeaheadOverrideOptions
}"></input-typeahead>
* The parameters are documented below.
*
* Notes:
* - dynamic:true by default, this allows choices to change after initialization.
* To turn it off, use:
* typeaheadOverrideOptions: { dynamic:false }
*
*/
import "jquery-typeahead";
import template from "./input-typeahead.html";
/**
* Helper class for ko component registration
*/
export class InputTypeaheadComponent {
constructor() {
return {
viewModel: InputTypeaheadViewModel,
template
};
}
}
export interface Item {
caption: string;
value: any;
}
/**
* Parameters for this component
*/
interface InputTypeaheadParams {
/**
* List of choices available in the dropdown.
*/
choices: ko.ObservableArray<Item>;
/**
* Gets updated when user clicks on the choice in the dropdown
*/
selection?: ko.Observable<Item>;
/**
* The current string value of <input>
*/
inputValue?: ko.Observable<string>;
/**
* Define what text you want as the input placeholder
*/
placeholder: string;
/**
* Override default jquery-typeahead options
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
*/
typeaheadOverrideOptions?: any;
/**
* This function gets called when pressing ENTER on the input box
*/
submitFct?: (inputValue: string | null, selection: Item | null) => void;
/**
* Typehead comes with a Search button that we normally remove.
* If you want to use it, turn this on
*/
showSearchButton?: boolean;
}
interface OnClickItem {
matchedKey: string;
value: any;
caption: string;
group: string;
}
interface Cache {
inputValue: string | null;
selection: Item | null;
}
class InputTypeaheadViewModel {
private static instanceCount = 0; // Generate unique id for each component's typeahead instance
private instanceNumber: number;
private params: InputTypeaheadParams;
private cache: Cache;
public constructor(params: InputTypeaheadParams) {
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
this.params = params;
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
this.cache = {
inputValue: null,
selection: null
};
}
/**
* Must execute once ko is rendered, so that it can find the input element by id
*/
private initializeTypeahead() {
let params = this.params;
let cache = this.cache;
let options: any = {
input: `#${this.getComponentId()}`, //'.input-typeahead',
order: "asc",
minLength: 0,
searchOnFocus: true,
source: {
display: "caption",
data: () => {
return this.params.choices();
}
},
callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
cache.selection = item;
if (params.selection) {
params.selection(item);
}
},
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
cache.inputValue = query;
if (params.inputValue) {
params.inputValue(query);
}
}
},
template: (query: string, item: any) => {
// Don't display id if caption *IS* the id
return item.caption === item.value
? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
},
dynamic: true
};
// Override options
if (params.typeaheadOverrideOptions) {
for (let p in params.typeaheadOverrideOptions) {
options[p] = params.typeaheadOverrideOptions[p];
}
}
($ as any).typeahead(options);
}
/**
* Get this component id
* @return unique id per instance
*/
private getComponentId(): string {
return `input-typeahead${this.instanceNumber}`;
}
/**
* Executed once ko is done rendering bindings
* Use ko's "template: afterRender" callback to do that without actually using any template.
* Another way is to call it within setTimeout() in constructor.
*/
public afterRender(): void {
this.initializeTypeahead();
}
public submit(): void {
if (this.params.submitFct) {
this.params.submitFct(this.cache.inputValue, this.cache.selection);
}
}
}
/**
* How to use this component:
*
* In your html markup, use:
* <input-typeahead params="{
choices:choices,
selection:selection,
inputValue:inputValue,
placeholder:'Enter source',
typeaheadOverrideOptions:typeaheadOverrideOptions
}"></input-typeahead>
* The parameters are documented below.
*
* Notes:
* - dynamic:true by default, this allows choices to change after initialization.
* To turn it off, use:
* typeaheadOverrideOptions: { dynamic:false }
*
*/
import "jquery-typeahead";
import template from "./input-typeahead.html";
/**
* Helper class for ko component registration
*/
export class InputTypeaheadComponent {
constructor() {
return {
viewModel: InputTypeaheadViewModel,
template,
};
}
}
export interface Item {
caption: string;
value: any;
}
/**
* Parameters for this component
*/
interface InputTypeaheadParams {
/**
* List of choices available in the dropdown.
*/
choices: ko.ObservableArray<Item>;
/**
* Gets updated when user clicks on the choice in the dropdown
*/
selection?: ko.Observable<Item>;
/**
* The current string value of <input>
*/
inputValue?: ko.Observable<string>;
/**
* Define what text you want as the input placeholder
*/
placeholder: string;
/**
* Override default jquery-typeahead options
* WARNING: do not override input, source or callback to avoid breaking the components behavior.
*/
typeaheadOverrideOptions?: any;
/**
* This function gets called when pressing ENTER on the input box
*/
submitFct?: (inputValue: string | null, selection: Item | null) => void;
/**
* Typehead comes with a Search button that we normally remove.
* If you want to use it, turn this on
*/
showSearchButton?: boolean;
}
interface OnClickItem {
matchedKey: string;
value: any;
caption: string;
group: string;
}
interface Cache {
inputValue: string | null;
selection: Item | null;
}
class InputTypeaheadViewModel {
private static instanceCount = 0; // Generate unique id for each component's typeahead instance
private instanceNumber: number;
private params: InputTypeaheadParams;
private cache: Cache;
public constructor(params: InputTypeaheadParams) {
this.instanceNumber = InputTypeaheadViewModel.instanceCount++;
this.params = params;
this.params.choices.subscribe(this.initializeTypeahead.bind(this));
this.cache = {
inputValue: null,
selection: null,
};
}
/**
* Must execute once ko is rendered, so that it can find the input element by id
*/
private initializeTypeahead() {
let params = this.params;
let cache = this.cache;
let options: any = {
input: `#${this.getComponentId()}`, //'.input-typeahead',
order: "asc",
minLength: 0,
searchOnFocus: true,
source: {
display: "caption",
data: () => {
return this.params.choices();
},
},
callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
cache.selection = item;
if (params.selection) {
params.selection(item);
}
},
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
cache.inputValue = query;
if (params.inputValue) {
params.inputValue(query);
}
},
},
template: (query: string, item: any) => {
// Don't display id if caption *IS* the id
return item.caption === item.value
? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
},
dynamic: true,
};
// Override options
if (params.typeaheadOverrideOptions) {
for (let p in params.typeaheadOverrideOptions) {
options[p] = params.typeaheadOverrideOptions[p];
}
}
($ as any).typeahead(options);
}
/**
* Get this component id
* @return unique id per instance
*/
private getComponentId(): string {
return `input-typeahead${this.instanceNumber}`;
}
/**
* Executed once ko is done rendering bindings
* Use ko's "template: afterRender" callback to do that without actually using any template.
* Another way is to call it within setTimeout() in constructor.
*/
public afterRender(): void {
this.initializeTypeahead();
}
public submit(): void {
if (this.params.submitFct) {
this.params.submitFct(this.cache.inputValue, this.cache.selection);
}
}
}

View File

@@ -8,10 +8,10 @@ describe("inputTypeahead", () => {
const props: InputTypeaheadComponentProps = {
choices: [
{ caption: "item1", value: "value1" },
{ caption: "item2", value: "value2" }
{ caption: "item2", value: "value2" },
],
placeholder: "placeholder",
useTextarea: false
useTextarea: false,
};
const wrapper = shallow(<InputTypeaheadComponent {...props} />);
@@ -22,10 +22,10 @@ describe("inputTypeahead", () => {
const props: InputTypeaheadComponentProps = {
choices: [
{ caption: "item1", value: "value1" },
{ caption: "item2", value: "value2" }
{ caption: "item2", value: "value2" },
],
placeholder: "placeholder",
useTextarea: true
useTextarea: true,
};
const wrapper = shallow(<InputTypeaheadComponent {...props} />);

View File

@@ -103,7 +103,7 @@ export class InputTypeaheadComponent extends React.Component<
super(props);
this.cache = {
inputValue: null,
selection: null
selection: null,
};
}
@@ -138,7 +138,7 @@ export class InputTypeaheadComponent extends React.Component<
className="input-typehead"
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onKeyDown(event)}
>
<div className="typeahead__container" ref={input => (this.containerElt = input)}>
<div className="typeahead__container" ref={(input) => (this.containerElt = input)}>
<div className="typeahead__field">
<span className="typeahead__query">
{this.props.useTextarea ? (
@@ -147,7 +147,7 @@ export class InputTypeaheadComponent extends React.Component<
name="q"
autoComplete="off"
aria-label="Input query"
ref={input => (this.inputElt = input)}
ref={(input) => (this.inputElt = input)}
defaultValue={this.props.defaultValue}
/>
) : (
@@ -156,7 +156,7 @@ export class InputTypeaheadComponent extends React.Component<
type="search"
autoComplete="off"
aria-label="Input query"
ref={input => (this.inputElt = input)}
ref={(input) => (this.inputElt = input)}
defaultValue={this.props.defaultValue}
/>
)}
@@ -181,9 +181,7 @@ export class InputTypeaheadComponent extends React.Component<
event.preventDefault();
event.stopPropagation();
this.props.submitFct(this.cache.inputValue, this.cache.selection);
$(this.containerElt)
.children(".typeahead__result")
.hide();
$(this.containerElt).children(".typeahead__result").hide();
}
}
}
@@ -203,7 +201,7 @@ export class InputTypeaheadComponent extends React.Component<
display: "caption",
data: () => {
return props.choices;
}
},
},
callback: {
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
@@ -218,7 +216,7 @@ export class InputTypeaheadComponent extends React.Component<
if (props.onNewValue) {
props.onNewValue(query);
}
}
},
},
template: (query: string, item: any) => {
// Don't display id if caption *IS* the id
@@ -226,7 +224,7 @@ export class InputTypeaheadComponent extends React.Component<
? "<span>{{caption}}</span>"
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
},
dynamic: true
dynamic: true,
};
// Override options

View File

@@ -1,19 +1,19 @@
<span class="input-typeahead-container">
<form class="input-typehead" data-bind="submit:submit">
<div class="typeahead__container">
<div class="typeahead__field">
<span class="typeahead__query">
<input
name="q"
type="search"
autocomplete="off"
data-bind="attr: { placeholder: params.placeholder, id:getComponentId() }, value:params.inputValue, template: { afterRender:afterRender() }"
/>
</span>
<span class="typeahead__button" data-bind="visible:params.showSearchButton">
<button type="submit"><span class="typeahead__search-icon"></span></button>
</span>
</div>
</div>
</form>
</span>
<span class="input-typeahead-container">
<form class="input-typehead" data-bind="submit:submit">
<div class="typeahead__container">
<div class="typeahead__field">
<span class="typeahead__query">
<input
name="q"
type="search"
autocomplete="off"
data-bind="attr: { placeholder: params.placeholder, id:getComponentId() }, value:params.inputValue, template: { afterRender:afterRender() }"
/>
</span>
<span class="typeahead__button" data-bind="visible:params.showSearchButton">
<button type="submit"><span class="typeahead__search-icon"></span></button>
</span>
</div>
</div>
</form>
</span>

View File

@@ -1,173 +1,173 @@
import Q from "q";
import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import template from "./json-editor-component.html";
/**
* Helper class for ko component registration
*/
export class JsonEditorComponent {
constructor() {
return {
viewModel: JsonEditorViewModel,
template
};
}
}
/**
* Parameters for this component
*/
export interface JsonEditorParams {
content: ViewModels.Editable<string>; // Sets the initial content of the editor
isReadOnly: boolean;
ariaLabel: string; // Sets what will be read to the user to define the control
updatedContent?: ViewModels.Editable<string>; // Gets updated when user edits
selectedContent?: ViewModels.Editable<string>; // Gets updated when user selects content from the editor
lineNumbers?: monaco.editor.IEditorOptions["lineNumbers"];
theme?: string; // Monaco editor theme
}
/**
* JSON Editor:
* A ko wrapper for the Monaco editor
*
* How to use in your markup:
* <json-editor params="{ isReadOnly:true, content:myJsonString, ariaLabel: myDescriptiveAriaLabel }"></json-editor>
*
* In writable mode, if you want to get changes to the content pass updatedContent and subscribe to it.
* content and updateContent are different to prevent circular updates.
*/
export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected editorContainer: HTMLElement;
protected params: JsonEditorParams;
private static instanceCount = 0; // Generate unique id to get different monaco editor
private editor: monaco.editor.IStandaloneCodeEditor;
private instanceNumber: number;
private resizer: EventListenerOrEventListenerObject;
private observer: MutationObserver;
private offsetWidth: number;
private offsetHeight: number;
private selectionListener: monaco.IDisposable;
private latestContentVersionId: number;
public constructor(params: JsonEditorParams) {
super();
this.instanceNumber = JsonEditorViewModel.instanceCount++;
this.params = params;
this.params.content.subscribe((newValue: string) => {
if (newValue) {
if (!!this.editor) {
this.editor.getModel().setValue(newValue);
} else {
this.createEditor(newValue, this.configureEditor.bind(this));
}
}
});
const onObserve: MutationCallback = (mutations: MutationRecord[], observer: MutationObserver): void => {
if (
this.offsetWidth !== this.editorContainer.offsetWidth ||
this.offsetHeight !== this.editorContainer.offsetHeight
) {
this.editor.layout();
this.offsetWidth = this.editorContainer.offsetWidth;
this.offsetHeight = this.editorContainer.offsetHeight;
}
};
this.observer = new MutationObserver(onObserve);
}
protected getEditorId(): string {
return `jsoneditor${this.instanceNumber}`;
}
/**
* Create the monaco editor and attach to DOM
*/
protected createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
this.registerCompletionItemProvider();
this.editorContainer = document.getElementById(this.getEditorId());
const options: monaco.editor.IEditorConstructionOptions = {
value: content,
language: this.getEditorLanguage(),
readOnly: this.params.isReadOnly,
lineNumbers: this.params.lineNumbers || "off",
fontSize: 12,
ariaLabel: this.params.ariaLabel,
theme: this.params.theme
};
this.editorContainer.innerHTML = "";
createCallback(monaco.editor.create(this.editorContainer, options));
}
// Interface. Will be implemented in children editor view model such as EditorViewModel.
protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return Q.Promise(() => {});
}
protected getEditorLanguage(): string {
return "json";
}
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor;
const queryEditorModel = this.editor.getModel();
if (!this.params.isReadOnly && this.params.updatedContent) {
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
const queryEditorModel = this.editor.getModel();
this.params.updatedContent(queryEditorModel.getValue());
});
}
if (this.params.selectedContent) {
this.selectionListener = this.editor.onDidChangeCursorSelection(
(event: monaco.editor.ICursorSelectionChangedEvent) => {
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection);
this.params.selectedContent(selectedContent);
}
);
}
this.resizer = () => {
editor.layout();
};
window.addEventListener("resize", this.resizer);
this.offsetHeight = this.editorContainer.offsetHeight;
this.offsetWidth = this.editorContainer.offsetWidth;
this.observer.observe(document.body, {
attributes: true,
subtree: true,
childList: true
});
this.editor.getModel().onDidChangeContent(async (e: monaco.editor.IModelContentChangedEvent) => {
if (!(<any>e).isFlush) {
return;
}
this.latestContentVersionId = e.versionId;
let input = (<any>e).changes[0].text;
let marks = await this.getErrorMarkers(input);
if (e.versionId === this.latestContentVersionId) {
monaco.editor.setModelMarkers(this.editor.getModel(), "ErrorMarkerOwner", marks);
}
});
this.editor.focus();
}
private dispose() {
window.removeEventListener("resize", this.resizer);
this.selectionListener && this.selectionListener.dispose();
this.observer.disconnect();
}
}
import Q from "q";
import * as monaco from "monaco-editor";
import * as ViewModels from "../../../Contracts/ViewModels";
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
import template from "./json-editor-component.html";
/**
* Helper class for ko component registration
*/
export class JsonEditorComponent {
constructor() {
return {
viewModel: JsonEditorViewModel,
template,
};
}
}
/**
* Parameters for this component
*/
export interface JsonEditorParams {
content: ViewModels.Editable<string>; // Sets the initial content of the editor
isReadOnly: boolean;
ariaLabel: string; // Sets what will be read to the user to define the control
updatedContent?: ViewModels.Editable<string>; // Gets updated when user edits
selectedContent?: ViewModels.Editable<string>; // Gets updated when user selects content from the editor
lineNumbers?: monaco.editor.IEditorOptions["lineNumbers"];
theme?: string; // Monaco editor theme
}
/**
* JSON Editor:
* A ko wrapper for the Monaco editor
*
* How to use in your markup:
* <json-editor params="{ isReadOnly:true, content:myJsonString, ariaLabel: myDescriptiveAriaLabel }"></json-editor>
*
* In writable mode, if you want to get changes to the content pass updatedContent and subscribe to it.
* content and updateContent are different to prevent circular updates.
*/
export class JsonEditorViewModel extends WaitsForTemplateViewModel {
protected editorContainer: HTMLElement;
protected params: JsonEditorParams;
private static instanceCount = 0; // Generate unique id to get different monaco editor
private editor: monaco.editor.IStandaloneCodeEditor;
private instanceNumber: number;
private resizer: EventListenerOrEventListenerObject;
private observer: MutationObserver;
private offsetWidth: number;
private offsetHeight: number;
private selectionListener: monaco.IDisposable;
private latestContentVersionId: number;
public constructor(params: JsonEditorParams) {
super();
this.instanceNumber = JsonEditorViewModel.instanceCount++;
this.params = params;
this.params.content.subscribe((newValue: string) => {
if (newValue) {
if (!!this.editor) {
this.editor.getModel().setValue(newValue);
} else {
this.createEditor(newValue, this.configureEditor.bind(this));
}
}
});
const onObserve: MutationCallback = (mutations: MutationRecord[], observer: MutationObserver): void => {
if (
this.offsetWidth !== this.editorContainer.offsetWidth ||
this.offsetHeight !== this.editorContainer.offsetHeight
) {
this.editor.layout();
this.offsetWidth = this.editorContainer.offsetWidth;
this.offsetHeight = this.editorContainer.offsetHeight;
}
};
this.observer = new MutationObserver(onObserve);
}
protected getEditorId(): string {
return `jsoneditor${this.instanceNumber}`;
}
/**
* Create the monaco editor and attach to DOM
*/
protected createEditor(content: string, createCallback: (e: monaco.editor.IStandaloneCodeEditor) => void) {
this.registerCompletionItemProvider();
this.editorContainer = document.getElementById(this.getEditorId());
const options: monaco.editor.IEditorConstructionOptions = {
value: content,
language: this.getEditorLanguage(),
readOnly: this.params.isReadOnly,
lineNumbers: this.params.lineNumbers || "off",
fontSize: 12,
ariaLabel: this.params.ariaLabel,
theme: this.params.theme,
};
this.editorContainer.innerHTML = "";
createCallback(monaco.editor.create(this.editorContainer, options));
}
// Interface. Will be implemented in children editor view model such as EditorViewModel.
protected registerCompletionItemProvider() {}
// Interface. Will be implemented in children editor view model such as EditorViewModel.
protected getErrorMarkers(input: string): Q.Promise<monaco.editor.IMarkerData[]> {
return Q.Promise(() => {});
}
protected getEditorLanguage(): string {
return "json";
}
protected configureEditor(editor: monaco.editor.IStandaloneCodeEditor) {
this.editor = editor;
const queryEditorModel = this.editor.getModel();
if (!this.params.isReadOnly && this.params.updatedContent) {
queryEditorModel.onDidChangeContent((e: monaco.editor.IModelContentChangedEvent) => {
const queryEditorModel = this.editor.getModel();
this.params.updatedContent(queryEditorModel.getValue());
});
}
if (this.params.selectedContent) {
this.selectionListener = this.editor.onDidChangeCursorSelection(
(event: monaco.editor.ICursorSelectionChangedEvent) => {
const selectedContent: string = this.editor.getModel().getValueInRange(event.selection);
this.params.selectedContent(selectedContent);
}
);
}
this.resizer = () => {
editor.layout();
};
window.addEventListener("resize", this.resizer);
this.offsetHeight = this.editorContainer.offsetHeight;
this.offsetWidth = this.editorContainer.offsetWidth;
this.observer.observe(document.body, {
attributes: true,
subtree: true,
childList: true,
});
this.editor.getModel().onDidChangeContent(async (e: monaco.editor.IModelContentChangedEvent) => {
if (!(<any>e).isFlush) {
return;
}
this.latestContentVersionId = e.versionId;
let input = (<any>e).changes[0].text;
let marks = await this.getErrorMarkers(input);
if (e.versionId === this.latestContentVersionId) {
monaco.editor.setModelMarkers(this.editor.getModel(), "ErrorMarkerOwner", marks);
}
});
this.editor.focus();
}
private dispose() {
window.removeEventListener("resize", this.resizer);
this.selectionListener && this.selectionListener.dispose();
this.observer.disconnect();
}
}

View File

@@ -1 +1 @@
<div class="jsonEditor" data-bind="attr:{ id:getEditorId() }"></div>
<div class="jsonEditor" data-bind="attr:{ id:getEditorId() }"></div>

View File

@@ -1,158 +1,158 @@
import * as DataModels from "../../../Contracts/DataModels";
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null
},
tags: "testTags",
type: "testType"
};
};
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null
},
tags: "testTags",
type: "testType"
};
};
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
mongoEndpoint: "https://testMongoEndpoint.azure.com/"
},
tags: "testTags",
type: "testType"
};
};
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
documentEndpoint: null,
gremlinEndpoint: null,
tableEndpoint: null
},
tags: "testTags",
type: "testType"
};
};
const createTerminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/"
},
databaseAccount: createTestDatabaseAccount()
});
};
const createMongo32Terminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo"
},
databaseAccount: createTestMongo32DatabaseAccount()
});
};
const createMongo36Terminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo"
},
databaseAccount: createTestMongo36DatabaseAccount()
});
};
const createCassandraTerminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra"
},
databaseAccount: createTestCassandraDatabaseAccount()
});
};
describe("NotebookTerminalComponent", () => {
it("getTerminalParams: Test for terminal", () => {
const terminal: NotebookTerminalComponent = createTerminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([["terminal", "true"]])
);
});
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
const terminal: NotebookTerminalComponent = createMongo32Terminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host]
])
);
});
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
const terminal: NotebookTerminalComponent = createMongo36Terminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host]
])
);
});
it("getTerminalParams: Test for Cassandra terminal", () => {
const terminal: NotebookTerminalComponent = createCassandraTerminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host]
])
);
});
});
import * as DataModels from "../../../Contracts/DataModels";
import { NotebookTerminalComponent } from "./NotebookTerminalComponent";
const createTestDatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};
const createTestMongo32DatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};
const createTestMongo36DatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: null,
documentEndpoint: "https://testDocumentEndpoint.azure.com/",
gremlinEndpoint: null,
tableEndpoint: null,
mongoEndpoint: "https://testMongoEndpoint.azure.com/",
},
tags: "testTags",
type: "testType",
};
};
const createTestCassandraDatabaseAccount = (): DataModels.DatabaseAccount => {
return {
id: "testId",
kind: "testKind",
location: "testLocation",
name: "testName",
properties: {
cassandraEndpoint: "https://testCassandraEndpoint.azure.com/",
documentEndpoint: null,
gremlinEndpoint: null,
tableEndpoint: null,
},
tags: "testTags",
type: "testType",
};
};
const createTerminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/",
},
databaseAccount: createTestDatabaseAccount(),
});
};
const createMongo32Terminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
},
databaseAccount: createTestMongo32DatabaseAccount(),
});
};
const createMongo36Terminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/mongo",
},
databaseAccount: createTestMongo36DatabaseAccount(),
});
};
const createCassandraTerminal = (): NotebookTerminalComponent => {
return new NotebookTerminalComponent({
notebookServerInfo: {
authToken: "testAuthToken",
notebookServerEndpoint: "https://testNotebookServerEndpoint.azure.com/cassandra",
},
databaseAccount: createTestCassandraDatabaseAccount(),
});
};
describe("NotebookTerminalComponent", () => {
it("getTerminalParams: Test for terminal", () => {
const terminal: NotebookTerminalComponent = createTerminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([["terminal", "true"]])
);
});
it("getTerminalParams: Test for Mongo 3.2 terminal", () => {
const terminal: NotebookTerminalComponent = createMongo32Terminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.documentEndpoint).host],
])
);
});
it("getTerminalParams: Test for Mongo 3.6 terminal", () => {
const terminal: NotebookTerminalComponent = createMongo36Terminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.mongoEndpoint).host],
])
);
});
it("getTerminalParams: Test for Cassandra terminal", () => {
const terminal: NotebookTerminalComponent = createCassandraTerminal();
const params: Map<string, string> = terminal.getTerminalParams();
expect(params).toEqual(
new Map<string, string>([
["terminal", "true"],
["terminalEndpoint", new URL(terminal.props.databaseAccount.properties.cassandraEndpoint).host],
])
);
});
});

View File

@@ -20,7 +20,7 @@ describe("GalleryCardComponent", () => {
views: 0,
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
pendingScanJobIds: undefined,
},
isFavorite: false,
showDownload: true,
@@ -30,7 +30,7 @@ describe("GalleryCardComponent", () => {
onFavoriteClick: undefined,
onUnfavoriteClick: undefined,
onDownloadClick: undefined,
onDeleteClick: undefined
onDeleteClick: undefined,
};
const wrapper = shallow(<GalleryCardComponent {...props} />);

View File

@@ -12,7 +12,7 @@ import {
Button,
LinkBase,
Separator,
TooltipHost
TooltipHost,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient";
@@ -47,7 +47,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "short",
day: "numeric"
day: "numeric",
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
@@ -57,7 +57,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={event => this.onClick(event, this.props.onClick)}
onClick={(event) => this.onClick(event, this.props.onClick)}
>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona
@@ -81,7 +81,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
<Text variant="small" nowrap>
{this.props.data.tags?.map((tag, index, array) => (
<span key={tag}>
<Link onClick={event => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
<Link onClick={(event) => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "}
</span>
))}
@@ -92,8 +92,8 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall
}
paddingBottom: GalleryCardComponent.cardItemGapSmall,
},
}}
nowrap
>
@@ -117,8 +117,8 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig
}
marginRight: GalleryCardComponent.cardItemGapBig,
},
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
@@ -176,7 +176,7 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
iconProps={{ iconName }}
title={title}
ariaLabel={title}
onClick={event => this.onClick(event, activate)}
onClick={(event) => this.onClick(event, activate)}
/>
</TooltipHost>
);

View File

@@ -12,11 +12,11 @@ describe("CodeOfConductComponent", () => {
const junoClient = new JunoClient(undefined);
junoClient.acceptCodeOfConduct = jest.fn().mockReturnValue({
status: HttpStatusCodes.OK,
data: true
data: true,
});
codeOfConductProps = {
junoClient: junoClient,
onAcceptCodeOfConduct: jest.fn()
onAcceptCodeOfConduct: jest.fn(),
};
});
@@ -27,10 +27,7 @@ describe("CodeOfConductComponent", () => {
it("onAcceptedCodeOfConductCalled", async () => {
const wrapper = shallow(<CodeOfConductComponent {...codeOfConductProps} />);
wrapper
.find(".genericPaneSubmitBtn")
.first()
.simulate("click");
wrapper.find(".genericPaneSubmitBtn").first().simulate("click");
await Promise.resolve();
expect(codeOfConductProps.onAcceptCodeOfConduct).toBeCalled();
});

View File

@@ -24,7 +24,7 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
super(props);
this.state = {
readCodeOfConduct: false
readCodeOfConduct: false,
};
this.descriptionPara1 = "Azure CosmosDB Notebook Gallery - Code of Conduct and Privacy Statement";
@@ -81,11 +81,11 @@ export class CodeOfConductComponent extends React.Component<CodeOfConductCompone
styles={{
label: {
margin: 0,
padding: "2 0 2 0"
padding: "2 0 2 0",
},
text: {
fontSize: 12
}
fontSize: 12,
},
}}
label="I have read and accepted the code of conduct and privacy statement"
onChange={this.onChangeCheckbox}

View File

@@ -1,114 +1,114 @@
import * as React from "react";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps {
container?: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryAndNotebookViewerComponentState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
export class GalleryAndNotebookViewerComponent extends React.Component<
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponentState
> {
constructor(props: GalleryAndNotebookViewerComponentProps) {
super(props);
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public render(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
openNotebook: this.openNotebook,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
private onBackClick = (): void => {
this.setState({
notebookUrl: undefined
});
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
this.setState({
notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id),
galleryItem: data,
isFavorite
});
};
private onSelectedTabChange = (selectedTab: GalleryTab): void => {
this.setState({
selectedTab
});
};
private onSortByChange = (sortBy: SortBy): void => {
this.setState({
sortBy
});
};
private onSearchTextChange = (searchText: string): void => {
this.setState({
searchText
});
};
}
import * as React from "react";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps {
container?: Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryAndNotebookViewerComponentState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
export class GalleryAndNotebookViewerComponent extends React.Component<
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponentState
> {
constructor(props: GalleryAndNotebookViewerComponentProps) {
super(props);
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText,
};
}
public render(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems,
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
openNotebook: this.openNotebook,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange,
};
return <GalleryViewerComponent {...props} />;
}
private onBackClick = (): void => {
this.setState({
notebookUrl: undefined,
});
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag,
});
};
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
this.setState({
notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id),
galleryItem: data,
isFavorite,
});
};
private onSelectedTabChange = (selectedTab: GalleryTab): void => {
this.setState({
selectedTab,
});
};
private onSortByChange = (sortBy: SortBy): void => {
this.setState({
sortBy,
});
};
private onSearchTextChange = (searchText: string): void => {
this.setState({
searchText,
});
};
}

View File

@@ -1,23 +1,23 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent,
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -12,7 +12,7 @@ describe("GalleryViewerComponent", () => {
openNotebook: undefined,
onSelectedTabChange: undefined,
onSortByChange: undefined,
onSearchTextChange: undefined
onSearchTextChange: undefined,
};
const wrapper = shallow(<GalleryViewerComponent {...props} />);

View File

@@ -14,7 +14,7 @@ import {
PivotItem,
SearchBox,
Stack,
Text
Text,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGalleryItem, IJunoResponse, IPublicGalleryData, JunoClient } from "../../../Juno/JunoClient";
@@ -44,14 +44,14 @@ export enum GalleryTab {
OfficialSamples,
PublicGallery,
Favorites,
Published
Published,
}
export enum SortBy {
MostViewed,
MostDownloaded,
MostFavorited,
MostRecent
MostRecent,
}
interface GalleryViewerComponentState {
@@ -106,27 +106,27 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
sortBy: props.sortBy,
searchText: props.searchText,
dialogProps: undefined,
isCodeOfConductAccepted: undefined
isCodeOfConductAccepted: undefined,
};
this.sortingOptions = [
{
key: SortBy.MostViewed,
text: GalleryViewerComponent.mostViewedText
text: GalleryViewerComponent.mostViewedText,
},
{
key: SortBy.MostDownloaded,
text: GalleryViewerComponent.mostDownloadedText
text: GalleryViewerComponent.mostDownloadedText,
},
{
key: SortBy.MostRecent,
text: GalleryViewerComponent.mostRecentText
}
text: GalleryViewerComponent.mostRecentText,
},
];
if (this.props.container?.isGalleryPublishEnabled()) {
this.sortingOptions.push({
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText
text: GalleryViewerComponent.mostFavoritedText,
});
}
@@ -158,14 +158,14 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange,
selectedKey: GalleryTab[this.state.selectedTab]
selectedKey: GalleryTab[this.state.selectedTab],
};
const pivotItems = tabs.map(tab => {
const pivotItems = tabs.map((tab) => {
const pivotItemProps: IPivotItemProps = {
itemKey: GalleryTab[tab.tab],
style: { marginTop: 20 },
headerText: GalleryUtils.getTabTitle(tab.tab)
headerText: GalleryUtils.getTabTitle(tab.tab),
};
return (
@@ -201,7 +201,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createSamplesTab = (tab: GalleryTab, data: IGalleryItem[]): GalleryTabInfo => {
return {
tab,
content: this.createSearchBarHeader(this.createCardsTabContent(data))
content: this.createSearchBarHeader(this.createCardsTabContent(data)),
};
};
@@ -212,7 +212,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
): GalleryTabInfo {
return {
tab,
content: this.createPublicGalleryTabContent(data, acceptedCodeOfConduct)
content: this.createPublicGalleryTabContent(data, acceptedCodeOfConduct),
};
}
@@ -225,7 +225,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
"You have not liked anything",
"Like any notebook from Official Samples or Public gallery"
)
: this.createSearchBarHeader(this.createCardsTabContent(data))
: this.createSearchBarHeader(this.createCardsTabContent(data)),
};
}
@@ -238,7 +238,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
"You have not published anything",
"Publish your sample notebooks to share your published work with others"
)
: this.createPublishedNotebooksTabContent(data)
: this.createPublishedNotebooksTabContent(data),
};
};
@@ -342,7 +342,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<th>Name</th>
<th>Policy violations</th>
</tr>
{data.map(item => (
{data.map((item) => (
<tr key={`policy-violations-tr-${item.id}`}>
<td>{item.name}</td>
<td>{item.policyViolations.join(", ")}</td>
@@ -391,7 +391,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}
this.setState({
sampleNotebooks: this.sampleNotebooks && [...this.sort(sortBy, this.search(searchText, this.sampleNotebooks))]
sampleNotebooks: this.sampleNotebooks && [...this.sort(sortBy, this.search(searchText, this.sampleNotebooks))],
});
}
@@ -418,7 +418,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
this.setState({
publicNotebooks: this.publicNotebooks && [...this.sort(sortBy, this.search(searchText, this.publicNotebooks))],
isCodeOfConductAccepted: this.isCodeOfConductAccepted
isCodeOfConductAccepted: this.isCodeOfConductAccepted,
});
}
@@ -438,8 +438,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
this.setState({
favoriteNotebooks: this.favoriteNotebooks && [
...this.sort(sortBy, this.search(searchText, this.favoriteNotebooks))
]
...this.sort(sortBy, this.search(searchText, this.favoriteNotebooks)),
],
});
// Refresh favorite button state
@@ -464,14 +464,14 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
this.setState({
publishedNotebooks: this.publishedNotebooks && [
...this.sort(sortBy, this.search(searchText, this.publishedNotebooks))
]
...this.sort(sortBy, this.search(searchText, this.publishedNotebooks)),
],
});
}
private search(searchText: string, data: IGalleryItem[]): IGalleryItem[] {
if (searchText) {
return data?.filter(item => this.isGalleryItemPresent(searchText, item));
return data?.filter((item) => this.isGalleryItemPresent(searchText, item));
}
return data;
@@ -482,7 +482,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const searchData: string[] = [item.author.toUpperCase(), item.description.toUpperCase(), item.name.toUpperCase()];
if (item.tags) {
searchData.push(...item.tags.map(tag => tag.toUpperCase()));
searchData.push(...item.tags.map((tag) => tag.toUpperCase()));
}
for (const data of searchData) {
@@ -525,7 +525,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}
private replaceGalleryItem(item: IGalleryItem, items?: IGalleryItem[]): void {
const index = items?.findIndex(value => value.id === item.id);
const index = items?.findIndex((value) => value.id === item.id);
if (index !== -1) {
items?.splice(index, 1, item);
}
@@ -539,14 +539,14 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
return {
height: visibleRect.height,
itemCount: this.columnCount * this.rowCount
itemCount: this.columnCount * this.rowCount,
};
};
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
let isFavorite: boolean;
if (this.props.container?.isGalleryPublishEnabled()) {
isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
isFavorite = this.favoriteNotebooks?.find((item) => item.id === data.id) !== undefined;
}
const props: GalleryCardComponentProps = {
data,
@@ -558,7 +558,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data),
onDownloadClick: () => this.downloadItem(data),
onDeleteClick: () => this.deleteItem(data)
onDeleteClick: () => this.deleteItem(data),
};
return (
@@ -571,7 +571,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private loadTaggedItems = (tag: string): void => {
const searchText = tag;
this.setState({
searchText
searchText,
});
this.loadTabContent(this.state.selectedTab, searchText, this.state.sortBy, true);
@@ -591,18 +591,20 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private unfavoriteItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.unfavoriteItem(this.props.container, this.props.junoClient, data, (item: IGalleryItem) => {
this.favoriteNotebooks = this.favoriteNotebooks?.filter(value => value.id !== item.id);
this.favoriteNotebooks = this.favoriteNotebooks?.filter((value) => value.id !== item.id);
this.refreshSelectedTab(item);
});
};
private downloadItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, item => this.refreshSelectedTab(item));
GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, (item) =>
this.refreshSelectedTab(item)
);
};
private deleteItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, item => {
this.publishedNotebooks = this.publishedNotebooks?.filter(notebook => item.id !== notebook.id);
GalleryUtils.deleteItem(this.props.container, this.props.junoClient, data, (item) => {
this.publishedNotebooks = this.publishedNotebooks?.filter((notebook) => item.id !== notebook.id);
this.refreshSelectedTab(item);
});
};
@@ -612,7 +614,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const searchText: string = undefined;
this.setState({
selectedTab,
searchText
searchText,
});
this.loadTabContent(selectedTab, searchText, this.state.sortBy, false);
@@ -622,7 +624,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private onSearchBoxChange = (event?: React.ChangeEvent<HTMLInputElement>, newValue?: string): void => {
const searchText = newValue;
this.setState({
searchText
searchText,
});
this.loadTabContent(this.state.selectedTab, searchText, this.state.sortBy, true);
@@ -632,7 +634,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private onDropdownChange = (event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption): void => {
const sortBy = option.key as SortBy;
this.setState({
sortBy
sortBy,
});
this.loadTabContent(this.state.selectedTab, this.state.searchText, sortBy, true);

View File

@@ -20,7 +20,7 @@ describe("NotebookMetadataComponent", () => {
views: 0,
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
pendingScanJobIds: undefined,
},
isFavorite: false,
downloadButtonText: "Download",
@@ -28,7 +28,7 @@ describe("NotebookMetadataComponent", () => {
onDownloadClick: undefined,
onFavoriteClick: undefined,
onUnfavoriteClick: undefined,
onReportAbuseClick: undefined
onReportAbuseClick: undefined,
};
const wrapper = shallow(<NotebookMetadataComponent {...props} />);
@@ -52,7 +52,7 @@ describe("NotebookMetadataComponent", () => {
views: 0,
newCellId: undefined,
policyViolations: undefined,
pendingScanJobIds: undefined
pendingScanJobIds: undefined,
},
isFavorite: true,
downloadButtonText: "Download",
@@ -60,7 +60,7 @@ describe("NotebookMetadataComponent", () => {
onDownloadClick: undefined,
onFavoriteClick: undefined,
onUnfavoriteClick: undefined,
onReportAbuseClick: undefined
onReportAbuseClick: undefined,
};
const wrapper = shallow(<NotebookMetadataComponent {...props} />);

View File

@@ -10,7 +10,7 @@ import {
PersonaSize,
PrimaryButton,
Stack,
Text
Text,
} from "office-ui-fabric-react";
import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient";
@@ -35,7 +35,7 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "short",
day: "numeric"
day: "numeric",
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);

View File

@@ -44,7 +44,8 @@ interface NotebookViewerComponentState {
showProgressBar: boolean;
}
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
export class NotebookViewerComponent
extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
implements DialogHost {
private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -59,12 +60,12 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
isReadOnly: true,
cellEditorType: "monaco",
autoSaveInterval: 365 * 24 * 3600 * 1000, // There is no way to turn off auto-save, set to 1 year
contentProvider: contents.JupyterContentProvider // NotebookViewer only knows how to talk to Jupyter contents API
contentProvider: contents.JupyterContentProvider, // NotebookViewer only knows how to talk to Jupyter contents API
});
this.notebookComponentBootstrapper = new NotebookComponentBootstrapper({
notebookClient: this.clientManager,
contentRef: createContentRef()
contentRef: createContentRef(),
});
this.state = {
@@ -72,7 +73,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
dialogProps: undefined,
showProgressBar: true
showProgressBar: true,
};
this.loadNotebookContent();
@@ -148,7 +149,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs,
hidePrompts: this.props.hidePrompts
hidePrompts: this.props.hidePrompts,
})}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
@@ -173,7 +174,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
return {
galleryItem,
isFavorite
isFavorite,
};
}
@@ -205,25 +206,25 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
onCancel && onCancel();
},
choiceGroupProps,
textFieldProps
}
textFieldProps,
},
});
}
private favoriteItem = async (): Promise<void> => {
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
GalleryUtils.favoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) =>
this.setState({ galleryItem: item, isFavorite: true })
);
};
private unfavoriteItem = async (): Promise<void> => {
GalleryUtils.unfavoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
GalleryUtils.unfavoriteItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) =>
this.setState({ galleryItem: item, isFavorite: false })
);
};
private downloadItem = async (): Promise<void> => {
GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, (item) =>
this.setState({ galleryItem: item })
);
};

View File

@@ -1,292 +1,292 @@
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 * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import { QueriesClient } from "../../../Common/QueriesClient";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
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();
}
}
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 = 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(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
},
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
});
}
}
}
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 * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import { QueriesClient } from "../../../Common/QueriesClient";
import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
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();
}
}
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 = 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(),
error: getErrorMessage(error),
errorStack: getErrorStack(error),
},
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

@@ -1,33 +1,33 @@
/**
* 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";
import Explorer from "../../Explorer";
export class QueriesGridComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: 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()));
}
}
/**
* 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";
import Explorer from "../../Explorer";
export class QueriesGridComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: 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()));
}
}

View File

@@ -28,7 +28,7 @@ export class RadioSwitchComponent extends React.Component<RadioSwitchComponentPr
tabIndex={0}
key={choice.key}
onClick={() => this.onSelect(choice)}
onKeyPress={event => this.onKeyPress(event, choice)}
onKeyPress={(event) => this.onKeyPress(event, choice)}
>
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
<span className="caption">{choice.label}</span>

View File

@@ -9,7 +9,7 @@ import ko from "knockout";
import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer";
jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
getIndexTransformationProgress: jest.fn().mockReturnValue(undefined),
}));
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
@@ -20,20 +20,20 @@ jest.mock("../../../Common/dataAccess/updateCollection", () => ({
conflictResolutionPolicy: undefined,
changeFeedPolicy: undefined,
analyticalStorageTtl: undefined,
geospatialConfig: undefined
geospatialConfig: undefined,
} as DataModels.Collection),
updateMongoDBCollectionThroughRP: jest.fn().mockReturnValue({
id: undefined,
shardKey: undefined,
indexes: [],
analyticalStorageTtl: undefined
} as MongoDBCollectionResource)
analyticalStorageTtl: undefined,
} as MongoDBCollectionResource),
}));
import { updateOffer } from "../../../Common/dataAccess/updateOffer";
import { MongoDBCollectionResource } from "../../../Utils/arm/generatedClients/2020-04-01/types";
import Q from "q";
jest.mock("../../../Common/dataAccess/updateOffer", () => ({
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer)
updateOffer: jest.fn().mockReturnValue({} as DataModels.Offer),
}));
describe("SettingsComponent", () => {
@@ -49,8 +49,8 @@ describe("SettingsComponent", () => {
onUpdateTabsButtons: undefined,
getPendingNotification: Q.Promise<DataModels.Notification>(() => {
return;
})
})
}),
}),
};
it("renders", () => {
@@ -71,7 +71,7 @@ describe("SettingsComponent", () => {
isScaleSaveable: false,
isScaleDiscardable: false,
isSubSettingsSaveable: true,
isSubSettingsDiscardable: true
isSubSettingsDiscardable: true,
});
wrapper.update();
expect(settingsComponentInstance.isSaveSettingsButtonEnabled()).toEqual(true);
@@ -93,7 +93,7 @@ describe("SettingsComponent", () => {
manualThroughput: undefined,
minimumThroughput: 400,
id: "test",
offerReplacePending: false
offerReplacePending: false,
});
const props = { ...baseProps };
@@ -106,7 +106,7 @@ describe("SettingsComponent", () => {
userCanChangeProvisioningTypes: true,
isAutoPilotSelected: true,
wasAutopilotOriginallySet: false,
autoPilotThroughput: 1000
autoPilotThroughput: 1000,
});
wrapper.update();
expect(settingsComponentInstance.hasProvisioningTypeChanged()).toEqual(true);
@@ -141,7 +141,7 @@ describe("SettingsComponent", () => {
onDeleteDatabaseContextMenuClick: undefined,
readSettings: undefined,
onSettingsClick: undefined,
loadOffer: undefined
loadOffer: undefined,
} as ViewModels.Database;
newCollection.getDatabase = () => newDatabase;
newCollection.offer = ko.observable(undefined);
@@ -170,14 +170,14 @@ describe("SettingsComponent", () => {
tableEndpoint: undefined,
gremlinEndpoint: undefined,
cassandraEndpoint: undefined,
enableMultipleWriteLocations: true
}
enableMultipleWriteLocations: true,
},
});
const newCollection = { ...collection };
newCollection.container = newContainer;
newCollection.conflictResolutionPolicy = ko.observable({
mode: DataModels.ConflictResolutionMode.Custom,
conflictResolutionProcedure: undefined
conflictResolutionProcedure: undefined,
} as DataModels.ConflictResolutionPolicy);
const props = { ...baseProps };
@@ -193,7 +193,7 @@ describe("SettingsComponent", () => {
wrapper.update();
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
settingsComponentInstance.mongoDBCollectionResource = {
id: "id"
id: "id",
};
await settingsComponentInstance.onSaveClick();
expect(updateCollection).toBeCalled();
@@ -238,7 +238,7 @@ describe("SettingsComponent", () => {
wrapper.setState({
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.LastWriterWins,
conflictResolutionPolicyPath: conflictResolutionPolicyPath
conflictResolutionPolicyPath: conflictResolutionPolicyPath,
});
wrapper.update();
const settingsComponentInstance = wrapper.instance() as SettingsComponent;
@@ -248,7 +248,7 @@ describe("SettingsComponent", () => {
wrapper.setState({
conflictResolutionPolicyMode: DataModels.ConflictResolutionMode.Custom,
conflictResolutionPolicyProcedure: conflictResolutionPolicyProcedure
conflictResolutionPolicyProcedure: conflictResolutionPolicyProcedure,
});
wrapper.update();
conflictResolutionPolicy = settingsComponentInstance.getUpdatedConflictResolutionPolicy();

View File

@@ -16,7 +16,7 @@ import { mongoIndexingPolicyAADError } from "./SettingsRenderUtils";
import { ScaleComponent, ScaleComponentProps } from "./SettingsSubComponents/ScaleComponent";
import {
MongoIndexingPolicyComponent,
MongoIndexingPolicyComponentProps
MongoIndexingPolicyComponentProps,
} from "./SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent";
import {
hasDatabaseSharedThroughput,
@@ -30,11 +30,11 @@ import {
MongoIndexTypes,
parseConflictResolutionMode,
parseConflictResolutionProcedure,
getMongoNotification
getMongoNotification,
} from "./SettingsUtils";
import {
ConflictResolutionComponent,
ConflictResolutionComponentProps
ConflictResolutionComponentProps,
} from "./SettingsSubComponents/ConflictResolutionComponent";
import { SubSettingsComponent, SubSettingsComponentProps } from "./SettingsSubComponents/SubSettingsComponent";
import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric-react";
@@ -187,21 +187,21 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isConflictResolutionDirty: false,
initialNotification: undefined,
selectedTab: SettingsV2TabTypes.ScaleTab
selectedTab: SettingsV2TabTypes.ScaleTab,
};
this.saveSettingsButton = {
isEnabled: this.isSaveSettingsButtonEnabled,
isVisible: () => {
return true;
}
},
};
this.discardSettingsChangesButton = {
isEnabled: this.isDiscardSettingsButtonEnabled,
isVisible: () => {
return true;
}
},
};
}
@@ -234,7 +234,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (this.mongoDBCollectionResource) {
this.setState({
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes],
});
}
}
@@ -277,7 +277,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isAutoPilotSelected: true,
wasAutopilotOriginallySet: true,
autoPilotThroughput: autoscaleMaxThroughput,
autoPilotThroughputBaseline: autoscaleMaxThroughput
autoPilotThroughputBaseline: autoscaleMaxThroughput,
});
}
};
@@ -306,7 +306,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
databaseAccountName: this.container.databaseAccount()?.name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
tabTitle: this.props.settingsTab.tabTitle(),
});
const newCollection: DataModels.Collection = { ...this.collection.rawDataModel };
@@ -339,14 +339,14 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
newCollection.changeFeedPolicy =
this.changeFeedPolicyVisible && this.state.changeFeedPolicy === ChangeFeedPolicyState.On
? {
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration
retentionDuration: Constants.BackendDefaults.maxChangeFeedRetentionDuration,
}
: undefined;
newCollection.analyticalStorageTtl = this.getAnalyticalStorageTtl();
newCollection.geospatialConfig = {
type: this.state.geospatialConfigType
type: this.state.geospatialConfigType,
};
const conflictResolutionChanges: DataModels.ConflictResolutionPolicy = this.getUpdatedConflictResolutionPolicy();
@@ -376,7 +376,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isSubSettingsSaveable: false,
isSubSettingsDiscardable: false,
isIndexingPolicyDirty: false,
isConflictResolutionDirty: false
isConflictResolutionDirty: false,
});
}
@@ -385,7 +385,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const newMongoIndexes = this.getMongoIndexesToSave();
const newMongoCollection: MongoDBCollectionResource = {
...this.mongoDBCollectionResource,
indexes: newMongoIndexes
indexes: newMongoIndexes,
};
this.mongoDBCollectionResource = await updateMongoDBCollectionThroughRP(
@@ -399,7 +399,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isMongoIndexingPolicySaveable: false,
indexesToDrop: [],
indexesToAdd: [],
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes]
currentMongoIndexes: [...this.mongoDBCollectionResource.indexes],
});
traceSuccess(
Action.MongoIndexUpdated,
@@ -409,7 +409,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
tabTitle: this.props.settingsTab.tabTitle(),
},
startKey
);
@@ -424,7 +424,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -438,7 +438,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collectionId: this.collection.id(),
currentOffer: this.collection.offer(),
autopilotThroughput: this.state.isAutoPilotSelected ? this.state.autoPilotThroughput : undefined,
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput
manualThroughput: this.state.isAutoPilotSelected ? undefined : this.state.throughput,
};
if (this.hasProvisioningTypeChanged()) {
if (this.state.isAutoPilotSelected) {
@@ -453,12 +453,12 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (this.state.isAutoPilotSelected) {
this.setState({
autoPilotThroughput: updatedOffer.autoscaleMaxThroughput,
autoPilotThroughputBaseline: updatedOffer.autoscaleMaxThroughput
autoPilotThroughputBaseline: updatedOffer.autoscaleMaxThroughput,
});
} else {
this.setState({
throughput: updatedOffer.manualThroughput,
throughputBaseline: updatedOffer.manualThroughput
throughputBaseline: updatedOffer.manualThroughput,
});
}
}
@@ -473,7 +473,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collectionName: this.collection?.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
tabTitle: this.props.settingsTab.tabTitle(),
},
startKey
);
@@ -491,7 +491,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle(),
error: getErrorMessage(error),
errorStack: getErrorStack(error)
errorStack: getErrorStack(error),
},
startKey
);
@@ -501,7 +501,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
public onRevertClick = (): void => {
trace(Action.SettingsV2Discarded, ActionModifiers.Mark, {
message: "Settings Discarded"
message: "Settings Discarded",
});
this.setState({
@@ -528,7 +528,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
isIndexingPolicyDirty: false,
isMongoIndexingPolicySaveable: false,
isMongoIndexingPolicyDiscardable: false,
isConflictResolutionDirty: false
isConflictResolutionDirty: false,
});
};
@@ -564,7 +564,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
collectionName: this.collection.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.props.settingsTab.tabTitle()
tabTitle: this.props.settingsTab.tabTitle(),
},
this.props.settingsTab.onLoadStartKey
);
@@ -592,7 +592,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
const newMongoIndexWithType: AddMongoIndexProps = {
mongoIndex: { key: { keys: [description] } } as MongoIndex,
type: type,
notification: notification
notification: notification,
};
if (index === newIndexesToAdd.length) {
newIndexesToAdd.push(newMongoIndexWithType);
@@ -667,7 +667,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
}
const policy: DataModels.ConflictResolutionPolicy = {
mode: parseConflictResolutionMode(this.state.conflictResolutionPolicyMode)
mode: parseConflictResolutionMode(this.state.conflictResolutionPolicyMode),
};
if (
@@ -764,7 +764,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedure: conflictResolutionPolicyProcedure,
conflictResolutionPolicyProcedureBaseline: conflictResolutionPolicyProcedure,
geospatialConfigType: geoSpatialConfigType,
geospatialConfigTypeBaseline: geoSpatialConfigType
geospatialConfigTypeBaseline: geoSpatialConfigType,
});
};
@@ -779,7 +779,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.saveSettingsButton.isEnabled()
disabled: !this.saveSettingsButton.isEnabled(),
});
}
@@ -792,7 +792,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
commandButtonLabel: label,
ariaLabel: label,
hasPopup: false,
disabled: !this.discardSettingsChangesButton.isEnabled()
disabled: !this.discardSettingsChangesButton.isEnabled(),
});
}
return buttons;
@@ -827,7 +827,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
onMaxAutoPilotThroughputChange: this.onMaxAutoPilotThroughputChange,
onScaleSaveableChange: this.onScaleSaveableChange,
onScaleDiscardableChange: this.onScaleDiscardableChange,
initialNotification: this.props.settingsTab.pendingNotification()
initialNotification: this.props.settingsTab.pendingNotification(),
};
const subSettingsComponentProps: SubSettingsComponentProps = {
@@ -854,7 +854,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
changeFeedPolicyBaseline: this.state.changeFeedPolicyBaseline,
onChangeFeedPolicyChange: this.onChangeFeedPolicyChange,
onSubSettingsSaveableChange: this.onSubSettingsSaveableChange,
onSubSettingsDiscardableChange: this.onSubSettingsDiscardableChange
onSubSettingsDiscardableChange: this.onSubSettingsDiscardableChange,
};
const indexingPolicyComponentProps: IndexingPolicyComponentProps = {
@@ -866,7 +866,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
logIndexingPolicySuccessMessage: this.logIndexingPolicySuccessMessage,
indexTransformationProgress: this.state.indexTransformationProgress,
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange
onIndexingPolicyDirtyChange: this.onIndexingPolicyDirtyChange,
};
const mongoIndexingPolicyComponentProps: MongoIndexingPolicyComponentProps = {
@@ -880,7 +880,7 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
indexTransformationProgress: this.state.indexTransformationProgress,
refreshIndexTransformationProgress: this.refreshIndexTransformationProgress,
onMongoIndexingPolicySaveableChange: this.onMongoIndexingPolicySaveableChange,
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange
onMongoIndexingPolicyDiscardableChange: this.onMongoIndexingPolicyDiscardableChange,
};
const conflictResolutionPolicyComponentProps: ConflictResolutionComponentProps = {
@@ -895,37 +895,37 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
conflictResolutionPolicyProcedure: this.state.conflictResolutionPolicyProcedure,
conflictResolutionPolicyProcedureBaseline: this.state.conflictResolutionPolicyProcedureBaseline,
onConflictResolutionPolicyProcedureChange: this.onConflictResolutionPolicyProcedureChange,
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange
onConflictResolutionDirtyChange: this.onConflictResolutionDirtyChange,
};
const tabs: SettingsV2TabInfo[] = [];
if (!hasDatabaseSharedThroughput(this.collection) && this.collection.offer()) {
tabs.push({
tab: SettingsV2TabTypes.ScaleTab,
content: <ScaleComponent {...scaleComponentProps} />
content: <ScaleComponent {...scaleComponentProps} />,
});
}
tabs.push({
tab: SettingsV2TabTypes.SubSettingsTab,
content: <SubSettingsComponent {...subSettingsComponentProps} />
content: <SubSettingsComponent {...subSettingsComponentProps} />,
});
if (this.shouldShowIndexingPolicyEditor) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />
content: <IndexingPolicyComponent {...indexingPolicyComponentProps} />,
});
} else if (this.container.isPreferredApiMongoDB()) {
if (isEmpty(this.container.features())) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: mongoIndexingPolicyAADError
content: mongoIndexingPolicyAADError,
});
} else if (this.container.isEnableMongoCapabilityPresent()) {
tabs.push({
tab: SettingsV2TabTypes.IndexingPolicyTab,
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />
content: <MongoIndexingPolicyComponent {...mongoIndexingPolicyComponentProps} />,
});
}
}
@@ -933,20 +933,20 @@ export class SettingsComponent extends React.Component<SettingsComponentProps, S
if (this.hasConflictResolution()) {
tabs.push({
tab: SettingsV2TabTypes.ConflictResolutionTab,
content: <ConflictResolutionComponent {...conflictResolutionPolicyComponentProps} />
content: <ConflictResolutionComponent {...conflictResolutionPolicyComponentProps} />,
});
}
const pivotProps: IPivotProps = {
onLinkClick: this.onPivotChange,
selectedKey: SettingsV2TabTypes[this.state.selectedTab]
selectedKey: SettingsV2TabTypes[this.state.selectedTab],
};
const pivotItems = tabs.map(tab => {
const pivotItems = tabs.map((tab) => {
const pivotItemProps: IPivotItemProps = {
itemKey: SettingsV2TabTypes[tab.tab],
style: { marginTop: 20 },
headerText: getTabTitle(tab.tab)
headerText: getTabTitle(tab.tab),
};
return (

View File

@@ -22,7 +22,7 @@ import {
renderMongoIndexTransformationRefreshMessage,
ManualEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown
getRuPriceBreakdown,
} from "./SettingsRenderUtils";
class SettingsRenderUtilsTestComponent extends React.Component {
@@ -31,15 +31,15 @@ class SettingsRenderUtilsTestComponent extends React.Component {
{ key: "costType", name: "", fieldName: "costType", minWidth: 100, maxWidth: 200, isResizable: true },
{ key: "hourly", name: "Hourly", fieldName: "hourly", minWidth: 100, maxWidth: 200, isResizable: true },
{ key: "daily", name: "Daily", fieldName: "daily", minWidth: 100, maxWidth: 200, isResizable: true },
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true }
{ key: "monthly", name: "Monthly", fieldName: "monthly", minWidth: 100, maxWidth: 200, isResizable: true },
];
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
{
costType: <Text>Current Cost</Text>,
hourly: <Text>$ 1.02</Text>,
daily: <Text>$ 24.48</Text>,
monthly: <Text>$ 744.6</Text>
}
monthly: <Text>$ 744.6</Text>,
},
];
const priceBreakdown: PriceBreakdown = {
hourlyPrice: 1.02,
@@ -47,7 +47,7 @@ class SettingsRenderUtilsTestComponent extends React.Component {
monthlyPrice: 744.6,
pricePerRu: 0.00051,
currency: "RMB",
currencySign: "¥"
currencySign: "¥",
};
return (

View File

@@ -9,7 +9,7 @@ import {
getMultimasterMultiplier,
computeRUUsagePriceHourly,
getPricePerRu,
estimatedCostDisclaimer
estimatedCostDisclaimer,
} from "../../../Utils/PricingUtils";
import {
ITextFieldStyles,
@@ -38,7 +38,7 @@ import {
DetailsListLayoutMode,
IDetailsRowProps,
DetailsRow,
IDetailsColumnStyles
IDetailsColumnStyles,
} from "office-ui-fabric-react";
import { isDirtyTypes, isDirty } from "./SettingsUtils";
@@ -71,49 +71,49 @@ export const infoAndToolTipTextStyle: ITextStyles = { root: { fontSize: 14 } };
export const noLeftPaddingCheckBoxStyle: ICheckboxStyles = {
label: {
margin: 0,
padding: "2 0 2 0"
padding: "2 0 2 0",
},
text: {
fontSize: 12
}
fontSize: 12,
},
};
export const subComponentStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 20 }
tokens: { childrenGap: 20 },
};
export const titleAndInputStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 5 }
tokens: { childrenGap: 5 },
};
export const mongoWarningStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 5 }
tokens: { childrenGap: 5 },
};
export const mongoErrorMessageStyles: Partial<IMessageBarStyles> = { root: { marginLeft: 10 } };
export const createAndAddMongoIndexStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 5 }
tokens: { childrenGap: 5 },
};
export const addMongoIndexStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 10 }
tokens: { childrenGap: 10 },
};
export const checkBoxAndInputStackProps: Partial<IStackProps> = {
tokens: { childrenGap: 10 }
tokens: { childrenGap: 10 },
};
export const toolTipLabelStackTokens: IStackTokens = {
childrenGap: 6
childrenGap: 6,
};
export const accordionStackTokens: IStackTokens = {
childrenGap: 10
childrenGap: 10,
};
export const addMongoIndexSubElementsTokens: IStackTokens = {
childrenGap: 20
childrenGap: 20,
};
export const accordionIconStyles: IIconStyles = { root: { paddingTop: 7 } };
@@ -128,30 +128,30 @@ export const transparentDetailsRowStyles: Partial<IDetailsRowStyles> = {
root: {
selectors: {
":hover": {
background: "transparent"
}
}
}
background: "transparent",
},
},
},
};
export const transparentDetailsHeaderStyle: Partial<IDetailsColumnStyles> = {
root: {
selectors: {
":hover": {
background: "transparent"
}
}
}
background: "transparent",
},
},
},
};
export const customDetailsListStyles: Partial<IDetailsListStyles> = {
root: {
selectors: {
".ms-FocusZone": {
paddingTop: 0
}
}
}
paddingTop: 0,
},
},
},
};
export const separatorStyles: Partial<ISeparatorStyles> = {
@@ -159,16 +159,16 @@ export const separatorStyles: Partial<ISeparatorStyles> = {
{
selectors: {
"::before": {
background: StyleConstants.BaseMedium
}
}
}
]
background: StyleConstants.BaseMedium,
},
},
},
],
};
export const messageBarStyles: Partial<IMessageBarStyles> = {
root: { marginTop: "5px", backgroundColor: "white" },
text: { fontSize: 14 }
text: { fontSize: 14 },
};
export const throughputUnit = "RU/s";
@@ -224,7 +224,7 @@ export const getRuPriceBreakdown = (
requestUnits: throughput,
numberOfRegions: numberOfRegions,
multimasterEnabled: isMultimaster,
isAutoscale: isAutoscale
isAutoscale: isAutoscale,
});
const basePricePerRu: number = isAutoscale
? getAutoscalePricePerRu(serverId, getMultimasterMultiplier(numberOfRegions, isMultimaster))
@@ -235,7 +235,7 @@ export const getRuPriceBreakdown = (
monthlyPrice: hourlyPrice * hoursInAMonth,
pricePerRu: basePricePerRu * getMultimasterMultiplier(numberOfRegions, isMultimaster),
currency: getPriceCurrency(serverId),
currencySign: getCurrencySign(serverId)
currencySign: getCurrencySign(serverId),
};
};
@@ -488,10 +488,10 @@ export const getTextFieldStyles = (current: isDirtyTypes, baseline: isDirtyTypes
selectors: {
":disabled": {
backgroundColor: StyleConstants.BaseMedium,
borderColor: StyleConstants.BaseMediumHigh
}
}
}
borderColor: StyleConstants.BaseMediumHigh,
},
},
},
});
export const getChoiceGroupStyles = (current: isDirtyTypes, baseline: isDirtyTypes): Partial<IChoiceGroupStyles> => ({
@@ -499,18 +499,18 @@ export const getChoiceGroupStyles = (current: isDirtyTypes, baseline: isDirtyTyp
{
selectors: {
".ms-ChoiceField-field.is-checked::before": {
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : ""
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : "",
},
".ms-ChoiceField-field.is-checked::after": {
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : ""
borderColor: isDirty(current, baseline) ? StyleConstants.Dirty : "",
},
".ms-ChoiceField-wrapper label": {
whiteSpace: "nowrap",
fontSize: 14,
fontFamily: StyleConstants.DataExplorerFont,
padding: "2px 5px"
}
}
}
]
padding: "2px 5px",
},
},
},
],
});

View File

@@ -25,7 +25,7 @@ describe("ConflictResolutionComponent", () => {
},
onConflictResolutionDirtyChange: () => {
return;
}
},
};
it("Sproc text field displayed", () => {

View File

@@ -7,7 +7,7 @@ import {
conflictResolutionLwwTooltip,
conflictResolutionCustomToolTip,
subComponentStackProps,
getChoiceGroupStyles
getChoiceGroupStyles,
} from "../SettingsRenderUtils";
import { TextField, ITextFieldProps, Stack, IChoiceGroupOption, ChoiceGroup } from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
@@ -35,9 +35,9 @@ export class ConflictResolutionComponent extends React.Component<ConflictResolut
private conflictResolutionChoiceGroupOptions: IChoiceGroupOption[] = [
{
key: DataModels.ConflictResolutionMode.LastWriterWins,
text: "Last Write Wins (default)"
text: "Last Write Wins (default)",
},
{ key: DataModels.ConflictResolutionMode.Custom, text: "Merge Procedure (custom)" }
{ key: DataModels.ConflictResolutionMode.Custom, text: "Merge Procedure (custom)" },
];
componentDidMount(): void {

View File

@@ -8,7 +8,7 @@ describe("IndexingPolicyComponent", () => {
automatic: false,
indexingMode: "",
includedPaths: [],
excludedPaths: []
excludedPaths: [],
};
const baseProps: IndexingPolicyComponentProps = {
shouldDiscardIndexingPolicy: false,
@@ -27,7 +27,7 @@ describe("IndexingPolicyComponent", () => {
return;
},
indexTransformationProgress: undefined,
refreshIndexTransformationProgress: () => new Promise(jest.fn())
refreshIndexTransformationProgress: () => new Promise(jest.fn()),
};
it("renders", () => {

View File

@@ -33,7 +33,7 @@ export class IndexingPolicyComponent extends React.Component<
constructor(props: IndexingPolicyComponentProps) {
super(props);
this.state = {
indexingPolicyContentIsValid: true
indexingPolicyContentIsValid: true,
};
}
@@ -55,7 +55,7 @@ export class IndexingPolicyComponent extends React.Component<
this.createIndexingPolicyEditor();
} else {
this.indexingPolicyEditor.updateOptions({
readOnly: isIndexTransforming(this.props.indexTransformationProgress)
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
});
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
@@ -91,7 +91,7 @@ export class IndexingPolicyComponent extends React.Component<
value: value,
language: "json",
readOnly: isIndexTransforming(this.props.indexTransformationProgress),
ariaLabel: "Indexing Policy"
ariaLabel: "Indexing Policy",
});
if (this.indexingPolicyEditor) {
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();

View File

@@ -6,7 +6,7 @@ describe("IndexingPolicyRefreshComponent", () => {
it("renders", () => {
const props: IndexingPolicyRefreshComponentProps = {
indexTransformationProgress: 90,
refreshIndexTransformationProgress: () => new Promise(jest.fn())
refreshIndexTransformationProgress: () => new Promise(jest.fn()),
};
const wrapper = shallow(<IndexingPolicyRefreshComponent {...props} />);

View File

@@ -2,7 +2,7 @@ import * as React from "react";
import { MessageBar, MessageBarType } from "office-ui-fabric-react";
import {
mongoIndexTransformationRefreshingMessage,
renderMongoIndexTransformationRefreshMessage
renderMongoIndexTransformationRefreshMessage,
} from "../../SettingsRenderUtils";
import { handleError } from "../../../../../Common/ErrorHandlingUtils";
import { isIndexTransforming } from "../../SettingsUtils";
@@ -23,7 +23,7 @@ export class IndexingPolicyRefreshComponent extends React.Component<
constructor(props: IndexingPolicyRefreshComponentProps) {
super(props);
this.state = {
isRefreshing: false
isRefreshing: false,
};
}

View File

@@ -15,7 +15,7 @@ describe("AddMongoIndexComponent", () => {
},
onDiscard: () => {
return;
}
},
};
const wrapper = shallow(<AddMongoIndexComponent {...props} />);

View File

@@ -7,21 +7,21 @@ import {
TextField,
Dropdown,
IDropdownOption,
ITextField
ITextField,
} from "office-ui-fabric-react";
import {
addMongoIndexSubElementsTokens,
mongoErrorMessageStyles,
mongoWarningStackProps,
shortWidthDropDownStyles,
shortWidthTextFieldStyles
shortWidthTextFieldStyles,
} from "../../SettingsRenderUtils";
import {
getMongoIndexTypeText,
MongoIndexTypes,
MongoNotificationMessage,
MongoNotificationType,
MongoWildcardPlaceHolder
MongoWildcardPlaceHolder,
} from "../../SettingsUtils";
export interface AddMongoIndexComponentProps {
@@ -39,7 +39,7 @@ export class AddMongoIndexComponent extends React.Component<AddMongoIndexCompone
private indexTypes: IDropdownOption[] = [MongoIndexTypes.Single, MongoIndexTypes.Wildcard].map(
(value: MongoIndexTypes) => ({
text: getMongoIndexTypeText(value),
key: value
key: value,
})
);

View File

@@ -28,7 +28,7 @@ describe("MongoIndexingPolicyComponent", () => {
},
onMongoIndexingPolicyDiscardableChange: () => {
return;
}
},
};
it("renders", () => {
@@ -55,24 +55,24 @@ describe("MongoIndexingPolicyComponent", () => {
false,
false,
true,
sampleWarning
sampleWarning,
],
[
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
false,
false,
true,
undefined
undefined,
],
[
{ type: MongoNotificationType.Error, message: sampleError } as MongoNotificationMessage,
true,
false,
true,
undefined
undefined,
],
[undefined, false, true, true, undefined],
[undefined, true, true, true, undefined]
[undefined, true, true, true, undefined],
];
test.each(cases)(
@@ -87,7 +87,7 @@ describe("MongoIndexingPolicyComponent", () => {
const addMongoIndexProps = {
mongoIndex: { key: { keys: ["sampleKey"] } },
type: MongoIndexTypes.Single,
notification: notification
notification: notification,
};
let indexesToDrop: number[] = [];

View File

@@ -11,7 +11,7 @@ import {
MessageBarType,
Spinner,
SpinnerSize,
Separator
Separator,
} from "office-ui-fabric-react";
import {
addMongoIndexStackProps,
@@ -23,7 +23,7 @@ import {
separatorStyles,
indexingPolicynUnsavedWarningMessage,
infoAndToolTipTextStyle,
onRenderRow
onRenderRow,
} from "../../SettingsRenderUtils";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
import {
@@ -33,7 +33,7 @@ import {
MongoNotificationType,
getMongoIndexType,
getMongoIndexTypeText,
isIndexTransforming
isIndexTransforming,
} from "../../SettingsUtils";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
@@ -71,8 +71,8 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
fieldName: "actionButton",
minWidth: 100,
maxWidth: 200,
isResizable: true
}
isResizable: true,
},
];
private indexesToBeDroppedColumns: IColumn[] = [
@@ -84,8 +84,8 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
fieldName: "actionButton",
minWidth: 100,
maxWidth: 200,
isResizable: true
}
isResizable: true,
},
];
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
@@ -114,7 +114,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
return false;
}
const addErrorsExist = !!this.props.indexesToAdd.find(addMongoIndexProps => addMongoIndexProps.notification);
const addErrorsExist = !!this.props.indexesToAdd.find((addMongoIndexProps) => addMongoIndexProps.notification);
if (addErrorsExist) {
return false;
@@ -129,7 +129,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
public getMongoWarningNotificationMessage = (): JSX.Element => {
const warningMessage = this.props.indexesToAdd.find(
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
(addMongoIndexProps) => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
)?.notification.message;
if (warningMessage) {
@@ -172,7 +172,7 @@ export class MongoIndexingPolicyComponent extends React.Component<MongoIndexingP
mongoIndexDisplayProps = {
definition: <Text>{definition}</Text>,
type: <Text>{getMongoIndexTypeText(type)}</Text>,
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex)
actionButton: definition === MongoIndexIdField ? <></> : this.getActionButton(arrayPosition, isCurrentIndex),
};
}
return mongoIndexDisplayProps;

View File

@@ -40,8 +40,8 @@ describe("ScaleComponent", () => {
return;
},
initialNotification: {
description: `Throughput update for ${targetThroughput} ${throughputUnit}`
} as DataModels.Notification
description: `Throughput update for ${targetThroughput} ${throughputUnit}`,
} as DataModels.Notification,
};
it("renders with correct initial notification", () => {
@@ -59,12 +59,12 @@ describe("ScaleComponent", () => {
autoscaleMaxThroughput: maxThroughput,
minimumThroughput: 400,
id: "offer",
offerReplacePending: true
offerReplacePending: true,
});
const newProps = {
...baseProps,
initialNotification: undefined as DataModels.Notification,
collection: newCollection
collection: newCollection,
};
wrapper = shallow(<ScaleComponent {...newProps} />);
expect(wrapper.exists("#throughputApplyShortDelayMessage")).toEqual(true);
@@ -95,10 +95,10 @@ describe("ScaleComponent", () => {
capabilities: [
{
name: Constants.CapabilityNames.EnableAutoScale.toLowerCase(),
description: undefined
}
]
}
description: undefined,
},
],
},
});
const props = { ...baseProps, container: newContainer };
const scaleComponent = new ScaleComponent(props);

View File

@@ -12,7 +12,7 @@ import {
throughputUnit,
getThroughputApplyLongDelayMessage,
getThroughputApplyShortDelayMessage,
updateThroughputBeyondLimitWarningMessage
updateThroughputBeyondLimitWarningMessage,
} from "../SettingsRenderUtils";
import { hasDatabaseSharedThroughput } from "../SettingsUtils";
import * as AutoPilotUtils from "../../../../Utils/AutoPilotUtils";

View File

@@ -54,7 +54,7 @@ describe("SubSettingsComponent", () => {
},
onSubSettingsDiscardableChange: () => {
return;
}
},
};
it("renders", () => {

View File

@@ -9,7 +9,7 @@ import {
TtlOn,
TtlOff,
TtlOnNoDefault,
getSanitizedInputValue
getSanitizedInputValue,
} from "../SettingsUtils";
import Explorer from "../../../Explorer";
import { Int32 } from "../../../Panes/Tables/Validators/EntityPropertyValidationCommon";
@@ -21,7 +21,7 @@ import {
titleAndInputStackProps,
getChoiceGroupStyles,
ttlWarning,
messageBarStyles
messageBarStyles,
} from "../SettingsRenderUtils";
import { ToolTipLabelComponent } from "./ToolTipLabelComponent";
@@ -119,7 +119,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
private ttlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off" },
{ key: TtlType.OnNoDefault, text: "On (no default)" },
{ key: TtlType.On, text: "On" }
{ key: TtlType.On, text: "On" },
];
public getTtlValue = (value: string): TtlType => {
@@ -207,7 +207,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
private analyticalTtlChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: TtlType.Off, text: "Off", disabled: true },
{ key: TtlType.OnNoDefault, text: "On (no default)" },
{ key: TtlType.On, text: "On" }
{ key: TtlType.On, text: "On" },
];
private getAnalyticalStorageTtlComponent = (): JSX.Element => (
@@ -244,7 +244,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
private geoSpatialConfigTypeChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: GeospatialConfigType.Geography, text: "Geography" },
{ key: GeospatialConfigType.Geometry, text: "Geometry" }
{ key: GeospatialConfigType.Geometry, text: "Geometry" },
];
private getGeoSpatialComponent = (): JSX.Element => (
@@ -260,7 +260,7 @@ export class SubSettingsComponent extends React.Component<SubSettingsComponentPr
private changeFeedChoiceGroupOptions: IChoiceGroupOption[] = [
{ key: ChangeFeedPolicyState.Off, text: "Off" },
{ key: ChangeFeedPolicyState.On, text: "On" }
{ key: ChangeFeedPolicyState.On, text: "On" },
];
private getChangeFeedComponent = (): JSX.Element => {

View File

@@ -2,7 +2,7 @@ import { shallow } from "enzyme";
import React from "react";
import {
ThroughputInputAutoPilotV3Component,
ThroughputInputAutoPilotV3Props
ThroughputInputAutoPilotV3Props,
} from "./ThroughputInputAutoPilotV3Component";
import * as DataModels from "../../../../../Contracts/DataModels";
@@ -43,7 +43,7 @@ describe("ThroughputInputAutoPilotV3Component", () => {
onScaleDiscardableChange: () => {
return;
},
getThroughputWarningMessage: () => undefined
getThroughputWarningMessage: () => undefined,
};
it("throughput input visible", () => {

View File

@@ -16,7 +16,7 @@ import {
AutoscaleEstimatedSpendingDisplayProps,
PriceBreakdown,
getRuPriceBreakdown,
transparentDetailsHeaderStyle
transparentDetailsHeaderStyle,
} from "../../SettingsRenderUtils";
import {
Text,
@@ -29,7 +29,7 @@ import {
Link,
MessageBar,
FontIcon,
IColumn
IColumn,
} from "office-ui-fabric-react";
import { ToolTipLabelComponent } from "../ToolTipLabelComponent";
import { getSanitizedInputValue, IsComponentDirtyResult, isDirty } from "../../SettingsUtils";
@@ -95,7 +95,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
private autoPilotInputMaxValue: number;
private options: IChoiceGroupOption[] = [
{ key: "true", text: "Autoscale" },
{ key: "false", text: "Manual" }
{ key: "false", text: "Manual" },
];
componentDidMount(): void {
@@ -157,7 +157,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
this.state = {
spendAckChecked: this.props.spendAckChecked,
exceedFreeTierThroughput:
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400
this.props.isFreeTierAccount && !this.props.isAutoPilotSelected && this.props.throughput > 400,
};
this.step = this.props.step ?? ThroughputInputAutoPilotV3Component.defaultStep;
@@ -224,7 +224,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
styles: transparentDetailsHeaderStyle,
},
{
key: "minPerMonth",
@@ -233,7 +233,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
styles: transparentDetailsHeaderStyle,
},
{
key: "maxPerMonth",
@@ -242,8 +242,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
}
styles: transparentDetailsHeaderStyle,
},
];
const estimatedSpendingItems: AutoscaleEstimatedSpendingDisplayProps[] = [
{
@@ -257,8 +257,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
</Text>
)
}
),
},
];
if (newThroughput) {
@@ -288,7 +288,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
</b>
</Text>
)
),
});
}
@@ -318,7 +318,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
styles: transparentDetailsHeaderStyle,
},
{
key: "hourly",
@@ -327,7 +327,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
styles: transparentDetailsHeaderStyle,
},
{
key: "daily",
@@ -336,7 +336,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
styles: transparentDetailsHeaderStyle,
},
{
key: "monthly",
@@ -345,8 +345,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
minWidth: 100,
maxWidth: 200,
isResizable: true,
styles: transparentDetailsHeaderStyle
}
styles: transparentDetailsHeaderStyle,
},
];
const estimatedSpendingItems: ManualEstimatedSpendingDisplayProps[] = [
{
@@ -365,8 +365,8 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
<Text>
{prices.currencySign} {calculateEstimateNumber(prices.monthlyPrice)}
</Text>
)
}
),
},
];
if (newThroughput) {
@@ -403,7 +403,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
{newPrices.currencySign} {calculateEstimateNumber(newPrices.monthlyPrice)}
</b>
</Text>
)
),
});
}
@@ -462,7 +462,7 @@ export class ThroughputInputAutoPilotV3Component extends React.Component<
databaseName: this.props.databaseName,
collectionName: this.props.collectionName,
apiKind: userContext.defaultExperience,
dataExplorerArea: "Scale Tab V2"
dataExplorerArea: "Scale Tab V2",
});
};

View File

@@ -5,7 +5,7 @@ import { ToolTipLabelComponent, ToolTipLabelComponentProps } from "./ToolTipLabe
describe("ToolTipLabelComponent", () => {
const props: ToolTipLabelComponentProps = {
label: "sample tool tip label",
toolTipElement: <span>sample tool tip text</span>
toolTipElement: <span>sample tool tip text</span>,
};
it("renders", () => {

View File

@@ -13,7 +13,7 @@ import {
getMongoIndexTypeText,
SingleFieldText,
WildcardText,
isIndexTransforming
isIndexTransforming,
} from "./SettingsUtils";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
@@ -45,7 +45,7 @@ describe("SettingsUtils", () => {
onDeleteDatabaseContextMenuClick: undefined,
readSettings: undefined,
onSettingsClick: undefined,
loadOffer: undefined
loadOffer: undefined,
} as ViewModels.Database;
};
newCollection.offer(undefined);
@@ -67,7 +67,7 @@ describe("SettingsUtils", () => {
automatic: true,
indexingMode: "consistent",
includedPaths: [],
excludedPaths: []
excludedPaths: [],
} as DataModels.IndexingPolicy;
it("works on all types", () => {

View File

@@ -15,23 +15,23 @@ export const WildcardText = "Wildcard";
export enum ChangeFeedPolicyState {
Off = "Off",
On = "On"
On = "On",
}
export enum TtlType {
Off = "off",
On = "on",
OnNoDefault = "on-nodefault"
OnNoDefault = "on-nodefault",
}
export enum GeospatialConfigType {
Geography = "Geography",
Geometry = "Geometry"
Geometry = "Geometry",
}
export enum MongoIndexTypes {
Single = "Single",
Wildcard = "Wildcard"
Wildcard = "Wildcard",
}
export interface AddMongoIndexProps {
@@ -44,7 +44,7 @@ export enum SettingsV2TabTypes {
ScaleTab,
ConflictResolutionTab,
SubSettingsTab,
IndexingPolicyTab
IndexingPolicyTab,
}
export interface IsComponentDirtyResult {
@@ -54,7 +54,7 @@ export interface IsComponentDirtyResult {
export enum MongoNotificationType {
Warning = "Warning",
Error = "Error"
Error = "Error",
}
export interface MongoNotificationMessage {
@@ -155,19 +155,19 @@ export const getMongoNotification = (description: string, type: MongoIndexTypes)
if (description && !type) {
return {
type: MongoNotificationType.Warning,
message: "Please select a type for each index."
message: "Please select a type for each index.",
};
}
if (type && (!description || description.trim().length === 0)) {
return {
type: MongoNotificationType.Error,
message: "Please enter a field name."
message: "Please enter a field name.",
};
} else if (type === MongoIndexTypes.Wildcard && description?.indexOf("$**") === -1) {
return {
type: MongoNotificationType.Error,
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder
message: "Wildcard path is not present in the field name. Use a pattern like " + MongoWildcardPlaceHolder,
};
}

View File

@@ -15,7 +15,7 @@ export const collection = ({
automatic: true,
indexingMode: "default",
includedPaths: [],
excludedPaths: []
excludedPaths: [],
}),
uniqueKeyPolicy: {} as DataModels.UniqueKeyPolicy,
usageSizeInKB: ko.observable(100),
@@ -24,7 +24,7 @@ export const collection = ({
manualThroughput: 10000,
minimumThroughput: 6000,
id: "offer",
offerReplacePending: false
offerReplacePending: false,
}),
conflictResolutionPolicy: ko.observable<DataModels.ConflictResolutionPolicy>(
{} as DataModels.ConflictResolutionPolicy
@@ -37,10 +37,10 @@ export const collection = ({
partitionKey: {
paths: [],
kind: "hash",
version: 2
version: 2,
},
partitionKeyProperty: "partitionKey",
readSettings: () => {
return;
}
},
} as unknown) as ViewModels.Collection;

View File

@@ -10,8 +10,8 @@ describe("SmartUiComponent", () => {
message: "Start at $24/mo per database",
link: {
href: "https://aka.ms/azure-cosmos-db-pricing",
text: "More Details"
}
text: "More Details",
},
},
children: [
{
@@ -24,8 +24,8 @@ describe("SmartUiComponent", () => {
max: 500,
step: 10,
defaultValue: 400,
uiType: UiType.Spinner
}
uiType: UiType.Spinner,
},
},
{
id: "throughput2",
@@ -37,8 +37,8 @@ describe("SmartUiComponent", () => {
max: 500,
step: 10,
defaultValue: 400,
uiType: UiType.Slider
}
uiType: UiType.Slider,
},
},
{
id: "throughput3",
@@ -51,16 +51,16 @@ describe("SmartUiComponent", () => {
step: 10,
defaultValue: 400,
uiType: UiType.Spinner,
errorMessage: "label, truelabel and falselabel are required for boolean input 'throughput3'"
}
errorMessage: "label, truelabel and falselabel are required for boolean input 'throughput3'",
},
},
{
id: "containerId",
input: {
label: "Container id",
dataFieldName: "containerId",
type: "string"
}
type: "string",
},
},
{
id: "analyticalStore",
@@ -70,8 +70,8 @@ describe("SmartUiComponent", () => {
falseLabel: "Disabled",
defaultValue: true,
dataFieldName: "analyticalStore",
type: "boolean"
}
type: "boolean",
},
},
{
id: "database",
@@ -82,20 +82,20 @@ describe("SmartUiComponent", () => {
choices: [
{ label: "Database 1", key: "db1" },
{ label: "Database 2", key: "db2" },
{ label: "Database 3", key: "db3" }
{ label: "Database 3", key: "db3" },
],
defaultKey: "db2"
}
}
]
}
defaultKey: "db2",
},
},
],
},
};
it("should render", async () => {
const wrapper = shallow(
<SmartUiComponent descriptor={exampleData} currentValues={new Map()} onInputChange={undefined} />
);
await new Promise(resolve => setTimeout(resolve, 0));
await new Promise((resolve) => setTimeout(resolve, 0));
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -23,7 +23,7 @@ export type InputTypeValue = "number" | "string" | "boolean" | "object";
export enum UiType {
Spinner = "Spinner",
Slider = "Slider"
Slider = "Slider",
}
export type ChoiceItem = { label: string; key: string };
@@ -101,13 +101,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
private static readonly labelStyle = {
color: "#393939",
fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
fontSize: 12
fontSize: 12,
};
constructor(props: SmartUiComponentProps) {
super(props);
this.state = {
errors: new Map()
errors: new Map(),
};
}
@@ -140,10 +140,10 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
label: {
root: {
...SmartUiComponent.labelStyle,
fontWeight: 600
}
}
}
fontWeight: 600,
},
},
},
}}
/>
</div>
@@ -200,7 +200,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
min: min,
max: max,
ariaLabel: label,
step: step
step: step,
};
const value = this.props.currentValues.get(dataFieldName) as number;
@@ -211,15 +211,15 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
{...props}
id={`${input.dataFieldName}-spinner-input`}
value={value?.toString()}
onValidate={newValue => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={newValue => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={newValue => this.onDecrement(input, newValue, props.step, props.min)}
onValidate={(newValue) => this.onValidate(input, newValue, props.min, props.max)}
onIncrement={(newValue) => this.onIncrement(input, newValue, props.step, props.max)}
onDecrement={(newValue) => this.onDecrement(input, newValue, props.step, props.min)}
labelPosition={Position.top}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600
}
fontWeight: 600,
},
}}
/>
{this.state.errors.has(dataFieldName) && (
@@ -233,13 +233,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
<Slider
{...props}
value={value}
onChange={newValue => this.props.onInputChange(input, newValue)}
onChange={(newValue) => this.props.onInputChange(input, newValue)}
styles={{
titleLabel: {
...SmartUiComponent.labelStyle,
fontWeight: 600
fontWeight: 600,
},
valueLabel: SmartUiComponent.labelStyle
valueLabel: SmartUiComponent.labelStyle,
}}
/>
</div>
@@ -264,13 +264,13 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
{
label: input.falseLabel,
key: "false",
onSelect: () => this.props.onInputChange(input, false)
onSelect: () => this.props.onInputChange(input, false),
},
{
label: input.trueLabel,
key: "true",
onSelect: () => this.props.onInputChange(input, true)
}
onSelect: () => this.props.onInputChange(input, true),
},
]}
selectedKey={selectedKey}
/>
@@ -288,16 +288,16 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
selectedKey={value ? value : defaultKey}
onChange={(_, item: IDropdownOption) => this.props.onInputChange(input, item.key.toString())}
placeholder={placeholder}
options={choices.map(c => ({
options={choices.map((c) => ({
key: c.key,
text: c.label
text: c.label,
}))}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600
fontWeight: 600,
},
dropdown: SmartUiComponent.labelStyle
dropdown: SmartUiComponent.labelStyle,
}}
/>
);
@@ -334,7 +334,7 @@ export class SmartUiComponent extends React.Component<SmartUiComponentProps, Sma
{node.info && this.renderInfo(node.info as Info)}
{node.input && this.renderInput(node.input)}
</Stack.Item>
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
{node.children && node.children.map((child) => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack>
);
}

View File

@@ -58,7 +58,7 @@ export class TabComponent extends React.Component<TabComponentProps> {
as="span"
className={className}
role="presentation"
onActivated={e => this.setActiveTab(index)}
onActivated={(e) => this.setActiveTab(index)}
aria-label={`Select tab: ${tab.title}`}
>
{tab.title}

View File

@@ -204,13 +204,13 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
this.label = options.label || ko.observable<string>();
this.showAutoPilot = options.showAutoPilot !== undefined ? options.showAutoPilot : ko.observable<boolean>(true);
this.isAutoPilotSelected = options.isAutoPilotSelected || ko.observable<boolean>(false);
this.isAutoPilotSelected.subscribe(value => {
this.isAutoPilotSelected.subscribe((value) => {
TelemetryProcessor.trace(Action.ToggleAutoscaleSetting, ActionModifiers.Mark, {
changedSelectedValueTo: value ? ActionModifiers.ToggleAutoscaleOn : ActionModifiers.ToggleAutoscaleOff,
databaseAccountName: userContext.databaseAccount?.name,
subscriptionId: userContext.subscriptionId,
apiKind: userContext.defaultExperience,
dataExplorerArea: "Scale Tab V1"
dataExplorerArea: "Scale Tab V1",
});
});
@@ -310,5 +310,5 @@ export class ThroughputInputViewModel extends WaitsForTemplateViewModel {
export const ThroughputInputComponentAutoPilotV3 = {
viewModel: ThroughputInputViewModel,
template: ThroughputInputComponentAutoscaleV3
template: ThroughputInputComponentAutoscaleV3,
};

View File

@@ -163,7 +163,7 @@
</div>
<div class="freeTierInlineWarning" data-bind="visible: showFreeTierExceedThroughputWarning">
<span class="freeTierWarningIcon"><img src="/warning.svg" alt="Warning"/></span>
<span class="freeTierWarningIcon"><img src="/warning.svg" alt="Warning" /></span>
<span class="freeTierWarningMessage" data-bind="text: freeTierExceedThroughputWarning"></span>
</div>

View File

@@ -4,18 +4,18 @@ import { TreeComponent, TreeNode, TreeNodeComponent } from "./TreeComponent";
const buildChildren = (): TreeNode[] => {
const grandChild11: TreeNode = {
label: "ZgrandChild11"
label: "ZgrandChild11",
};
const grandChild12: TreeNode = {
label: "AgrandChild12"
label: "AgrandChild12",
};
const child1: TreeNode = {
label: "Bchild1",
children: [grandChild11, grandChild12]
children: [grandChild11, grandChild12],
};
const child2: TreeNode = {
label: "2child2"
label: "2child2",
};
return [child1, child2];
@@ -23,28 +23,28 @@ const buildChildren = (): TreeNode[] => {
const buildChildren2 = (): TreeNode[] => {
const grandChild11: TreeNode = {
label: "ZgrandChild11"
label: "ZgrandChild11",
};
const grandChild12: TreeNode = {
label: "AgrandChild12"
label: "AgrandChild12",
};
const child1: TreeNode = {
label: "aChild"
label: "aChild",
};
const child2: TreeNode = {
label: "bchild",
children: [grandChild11, grandChild12]
children: [grandChild11, grandChild12],
};
const child3: TreeNode = {
label: "cchild"
label: "cchild",
};
const child4: TreeNode = {
label: "dchild",
children: [grandChild11, grandChild12]
children: [grandChild11, grandChild12],
};
return [child1, child2, child3, child4];
@@ -54,12 +54,12 @@ describe("TreeComponent", () => {
it("renders a simple tree", () => {
const root = {
label: "root",
children: buildChildren()
children: buildChildren(),
};
const props = {
rootNode: root,
className: "tree"
className: "tree",
};
const wrapper = shallow(<TreeComponent {...props} />);
@@ -78,8 +78,8 @@ describe("TreeNodeComponent", () => {
label: "menuLabel",
onClick: undefined,
iconSrc: undefined,
isDisabled: true
}
isDisabled: true,
},
],
iconSrc: undefined,
isExpanded: true,
@@ -90,13 +90,13 @@ describe("TreeNodeComponent", () => {
isSelected: undefined,
onClick: undefined,
onExpanded: undefined,
onCollapsed: undefined
onCollapsed: undefined,
};
const props = {
node,
generation: 12,
paddingLeft: 23
paddingLeft: 23,
};
const wrapper = shallow(<TreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -106,12 +106,12 @@ describe("TreeNodeComponent", () => {
const node: TreeNode = {
label: "label",
children: buildChildren(),
isExpanded: true
isExpanded: true,
};
const props = {
node,
generation: 2,
paddingLeft: 9
paddingLeft: 9,
};
const wrapper = shallow(<TreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -121,12 +121,12 @@ describe("TreeNodeComponent", () => {
const node: TreeNode = {
label: "label",
children: buildChildren(),
isAlphaSorted: false
isAlphaSorted: false,
};
const props = {
node,
generation: 2,
paddingLeft: 9
paddingLeft: 9,
};
const wrapper = shallow(<TreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -148,13 +148,13 @@ describe("TreeNodeComponent", () => {
isSelected: undefined,
onClick: undefined,
onExpanded: undefined,
onCollapsed: undefined
onCollapsed: undefined,
};
const props = {
node,
generation: 12,
paddingLeft: 23
paddingLeft: 23,
};
const wrapper = shallow(<TreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -164,13 +164,13 @@ describe("TreeNodeComponent", () => {
const node: TreeNode = {
label: "label",
children: [],
isExpanded: true
isExpanded: true,
};
const props = {
node,
generation: 2,
paddingLeft: 9
paddingLeft: 9,
};
const wrapper = shallow(<TreeNodeComponent {...props} />);
expect(wrapper).toMatchSnapshot();

View File

@@ -12,7 +12,7 @@ import { IconButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
import {
DirectionalHint,
IContextualMenuItemProps,
IContextualMenuProps
IContextualMenuProps,
} from "office-ui-fabric-react/lib/ContextualMenu";
import TriangleDownIcon from "../../../../images/Triangle-down.svg";
@@ -89,7 +89,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
this.isExpanded = props.node.isExpanded;
this.state = {
isExpanded: props.node.isExpanded,
isMenuShowing: false
isMenuShowing: false,
};
}
@@ -105,7 +105,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
if (this.props.node.isExpanded !== this.isExpanded) {
this.isExpanded = this.props.node.isExpanded;
this.setState({
isExpanded: this.props.node.isExpanded
isExpanded: this.props.node.isExpanded,
});
}
}
@@ -124,8 +124,8 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
let unsortedChildren;
if (treeNode.isLeavesParentsSeparate) {
// Separate parents and leave
const parents: TreeNode[] = treeNode.children.filter(node => node.children);
const leaves: TreeNode[] = treeNode.children.filter(node => !node.children);
const parents: TreeNode[] = treeNode.children.filter((node) => node.children);
const leaves: TreeNode[] = treeNode.children.filter((node) => !node.children);
if (treeNode.isAlphaSorted) {
parents.sort(compareFct);
@@ -235,7 +235,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
private renderContextMenuButton(node: TreeNode): JSX.Element {
const menuItemLabel = "More";
const buttonStyles: Partial<IButtonStyles> = {
rootFocused: { outline: `1px dashed ${Constants.StyleConstants.FocusColor}` }
rootFocused: { outline: `1px dashed ${Constants.StyleConstants.FocusColor}` },
};
return (
@@ -246,7 +246,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
ariaLabel={menuItemLabel}
menuIconProps={{
iconName: menuItemLabel,
styles: { root: { fontSize: "18px", fontWeight: "bold" } }
styles: { root: { fontSize: "18px", fontWeight: "bold" } },
}}
menuProps={{
coverTarget: true,
@@ -261,7 +261,7 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
<div
data-test={`treeComponentMenuItemContainer`}
className="treeComponentMenuItemContainer"
onContextMenu={e => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
onContextMenu={(e) => e.target.dispatchEvent(TreeNodeComponent.createClickEvent())}
>
{props.item.onRenderIcon()}
<span
@@ -281,11 +281,11 @@ export class TreeNodeComponent extends React.Component<TreeNodeComponentProps, T
onClick: () => {
menuItem.onClick();
TelemetryProcessor.trace(Action.ClickResourceTreeNodeContextMenuItem, ActionModifiers.Mark, {
label: menuItem.label
label: menuItem.label,
});
},
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />
}))
onRenderIcon: (props: any) => <img src={menuItem.iconSrc} alt="" />,
})),
}}
styles={buttonStyles}
/>

View File

@@ -41,26 +41,26 @@ describe("ContainerSampleGenerator", () => {
data: [
{
firstname: "Eva",
age: 44
age: 44,
},
{
firstname: "Véronique",
age: 50
age: 50,
},
{
firstname: "亜妃子",
age: 5
age: 5,
},
{
firstname: "John",
age: 23
}
]
age: 23,
},
],
};
const collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray<ViewModels.Collection>([collection])
collections: ko.observableArray<ViewModels.Collection>([collection]),
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
@@ -87,9 +87,9 @@ describe("ContainerSampleGenerator", () => {
documentEndpoint: "bar",
gremlinEndpoint: "foo",
tableEndpoint: "foo",
cassandraEndpoint: "foo"
}
}
cassandraEndpoint: "foo",
},
},
});
const sampleCollectionId = "SampleCollection";
@@ -102,13 +102,13 @@ describe("ContainerSampleGenerator", () => {
createNewDatabase: true,
collectionId: sampleCollectionId,
data: [
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)"
]
"g.addV('person').property(id, '1').property('_partitionKey','pk').property('name', 'Eva').property('age', 44)",
],
};
const collection = { id: ko.observable(sampleCollectionId) } as ViewModels.Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray<ViewModels.Collection>([collection])
collections: ko.observableArray<ViewModels.Collection>([collection]),
} as ViewModels.Database;
database.findCollectionWithId = () => collection;
collection.databaseId = database.id();

View File

@@ -54,7 +54,7 @@ export class ContainerSampleGenerator {
private async createContainerAsync(): Promise<ViewModels.Collection> {
const createRequest: DataModels.CreateCollectionParams = {
...this.sampleDataFile
...this.sampleDataFile,
};
await createCollection(createRequest);
@@ -87,16 +87,16 @@ export class ContainerSampleGenerator {
databaseId: databaseId,
collectionId: collection.id(),
masterKey: userContext.masterKey || "",
maxResultSize: 100
maxResultSize: 100,
});
await queries
.map(query => () => gremlinClient.execute(query))
.map((query) => () => gremlinClient.execute(query))
.reduce((previous, current) => previous.then(current), Promise.resolve());
} else {
// For SQL all queries are executed at the same time
await Promise.all(
this.sampleDataFile.data.map(async doc => {
this.sampleDataFile.data.map(async (doc) => {
try {
await createDocument(collection, doc);
} catch (error) {

View File

@@ -13,7 +13,7 @@ describe("DataSampleUtils", () => {
const collection = { id: ko.observable(sampleCollectionId) } as Collection;
const database = {
id: ko.observable(sampleDatabaseId),
collections: ko.observableArray<Collection>([collection])
collections: ko.observableArray<Collection>([collection]),
} as Database;
const explorer = {} as Explorer;
explorer.nonSystemDatabases = ko.computed(() => [database]);

View File

@@ -26,7 +26,7 @@ export class DataSamplesUtil {
await generator
.createSampleContainerAsync()
.catch(error =>
.catch((error) =>
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Error creating sample container: ${error}`)
);
const msg = `The sample ${containerName} in database ${databaseName} has been successfully created.`;
@@ -48,10 +48,10 @@ export class DataSamplesUtil {
* @param containerDatabases
*/
public hasContainer(databaseName: string, containerName: string, containerDatabases: ViewModels.Database[]): boolean {
const filteredDatabases = containerDatabases.filter(database => database.id() === databaseName);
const filteredDatabases = containerDatabases.filter((database) => database.id() === databaseName);
return (
filteredDatabases.length > 0 &&
filteredDatabases[0].collections().filter(collection => collection.id() === containerName).length > 0
filteredDatabases[0].collections().filter((collection) => collection.id() === containerName).length > 0
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,158 +1,156 @@
import * as sinon from "sinon";
import { D3ForceGraph, LoadMoreDataAction, D3GraphNodeData } from "./D3ForceGraph";
import { D3Node, D3Link, GraphData } from "../GraphExplorerComponent/GraphData";
import GraphTab from "../../Tabs/GraphTab";
describe("D3ForceGraph", () => {
const v1Id = "v1";
const l1: D3Link = {
id: "id1",
inV: v1Id,
outV: "v2",
label: "l1",
source: null,
target: null
};
it("should count neighbors", () => {
const l2: D3Link = {
id: "id1",
inV: "v2",
outV: v1Id,
label: "l2",
source: null,
target: null
};
const l3: D3Link = {
id: "id1",
inV: v1Id,
outV: "v3",
label: "l3",
source: null,
target: null
};
const links = [l1, l2, l3];
const count = D3ForceGraph.countEdges(links);
expect(count.get(v1Id)).toBe(3);
expect(count.get("v2")).toBe(2);
expect(count.get("v3")).toBe(1);
});
describe("Behavior", () => {
let forceGraph: D3ForceGraph;
let rootNode: SVGSVGElement;
const newGraph: GraphData<D3Node, D3Link> = new GraphData();
newGraph.addVertex({
id: v1Id,
label: "vlabel1",
_isRoot: true
});
newGraph.addVertex({
id: "v2",
label: "vlabel2"
});
newGraph.addEdge(l1);
beforeAll(() => {
rootNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
rootNode.setAttribute("class", "maingraph");
});
afterAll(() => {
rootNode.remove();
});
beforeEach(() => {
forceGraph = new D3ForceGraph({
graphConfig: GraphTab.createGraphConfig(),
onHighlightedNode: sinon.spy(),
onLoadMoreData: (action: LoadMoreDataAction): void => {},
// parent to graph
onInitialized: sinon.spy(),
// For unit testing purposes
onGraphUpdated: null
});
forceGraph.init(rootNode);
});
afterEach(() => {
forceGraph.destroy();
});
it("should render graph d3 nodes and edges", done => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".nodes").length).toBe(1);
expect($(rootNode).find(".links").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render vertices (as circle)", done => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".node circle").length).toBe(2);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render vertex label", done => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(`text:contains(${v1Id})`).length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render root vertex", done => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".node.root").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render edge", done => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find("path.link").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should call onInitialized callback", () => {
expect((forceGraph.params.onInitialized as sinon.SinonSpy).calledOnce).toBe(true);
});
it("should call onHighlightedNode callback when mouse hovering over node", () => {
forceGraph.params.onGraphUpdated = () => {
const mouseoverEvent = document.createEvent("Events");
mouseoverEvent.initEvent("mouseover", true, false);
$(rootNode)
.find(".node")[0]
.dispatchEvent(mouseoverEvent); // [0] is v1 vertex
// onHighlightedNode is always called once to clear the selection
expect((forceGraph.params.onHighlightedNode as sinon.SinonSpy).calledTwice).toBe(true);
const onHighlightedNode = (forceGraph.params.onHighlightedNode as sinon.SinonSpy).args[1][0] as D3GraphNodeData;
expect(onHighlightedNode).not.toBe(null);
expect(onHighlightedNode.id).toEqual(v1Id);
};
forceGraph.updateGraph(newGraph);
});
});
});
import * as sinon from "sinon";
import { D3ForceGraph, LoadMoreDataAction, D3GraphNodeData } from "./D3ForceGraph";
import { D3Node, D3Link, GraphData } from "../GraphExplorerComponent/GraphData";
import GraphTab from "../../Tabs/GraphTab";
describe("D3ForceGraph", () => {
const v1Id = "v1";
const l1: D3Link = {
id: "id1",
inV: v1Id,
outV: "v2",
label: "l1",
source: null,
target: null,
};
it("should count neighbors", () => {
const l2: D3Link = {
id: "id1",
inV: "v2",
outV: v1Id,
label: "l2",
source: null,
target: null,
};
const l3: D3Link = {
id: "id1",
inV: v1Id,
outV: "v3",
label: "l3",
source: null,
target: null,
};
const links = [l1, l2, l3];
const count = D3ForceGraph.countEdges(links);
expect(count.get(v1Id)).toBe(3);
expect(count.get("v2")).toBe(2);
expect(count.get("v3")).toBe(1);
});
describe("Behavior", () => {
let forceGraph: D3ForceGraph;
let rootNode: SVGSVGElement;
const newGraph: GraphData<D3Node, D3Link> = new GraphData();
newGraph.addVertex({
id: v1Id,
label: "vlabel1",
_isRoot: true,
});
newGraph.addVertex({
id: "v2",
label: "vlabel2",
});
newGraph.addEdge(l1);
beforeAll(() => {
rootNode = document.createElementNS("http://www.w3.org/2000/svg", "svg");
rootNode.setAttribute("class", "maingraph");
});
afterAll(() => {
rootNode.remove();
});
beforeEach(() => {
forceGraph = new D3ForceGraph({
graphConfig: GraphTab.createGraphConfig(),
onHighlightedNode: sinon.spy(),
onLoadMoreData: (action: LoadMoreDataAction): void => {},
// parent to graph
onInitialized: sinon.spy(),
// For unit testing purposes
onGraphUpdated: null,
});
forceGraph.init(rootNode);
});
afterEach(() => {
forceGraph.destroy();
});
it("should render graph d3 nodes and edges", (done) => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".nodes").length).toBe(1);
expect($(rootNode).find(".links").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render vertices (as circle)", (done) => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".node circle").length).toBe(2);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render vertex label", (done) => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(`text:contains(${v1Id})`).length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render root vertex", (done) => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find(".node.root").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should render edge", (done) => {
forceGraph.params.onGraphUpdated = () => {
expect($(rootNode).find("path.link").length).toBe(1);
done();
};
forceGraph.updateGraph(newGraph);
});
it("should call onInitialized callback", () => {
expect((forceGraph.params.onInitialized as sinon.SinonSpy).calledOnce).toBe(true);
});
it("should call onHighlightedNode callback when mouse hovering over node", () => {
forceGraph.params.onGraphUpdated = () => {
const mouseoverEvent = document.createEvent("Events");
mouseoverEvent.initEvent("mouseover", true, false);
$(rootNode).find(".node")[0].dispatchEvent(mouseoverEvent); // [0] is v1 vertex
// onHighlightedNode is always called once to clear the selection
expect((forceGraph.params.onHighlightedNode as sinon.SinonSpy).calledTwice).toBe(true);
const onHighlightedNode = (forceGraph.params.onHighlightedNode as sinon.SinonSpy).args[1][0] as D3GraphNodeData;
expect(onHighlightedNode).not.toBe(null);
expect(onHighlightedNode.id).toEqual(v1Id);
};
forceGraph.updateGraph(newGraph);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -101,7 +101,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
this.props.editedNeighbors.addedEdges.push({
inputInV: inV,
inputOutV: EditorNeighborsComponent.DEFAULT_BLANK_VALUE,
label: EditorNeighborsComponent.DEFAULT_BLANK_VALUE
label: EditorNeighborsComponent.DEFAULT_BLANK_VALUE,
});
this.onUpdateEdges();
}
@@ -110,7 +110,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
this.props.editedNeighbors.addedEdges.push({
inputInV: EditorNeighborsComponent.DEFAULT_BLANK_VALUE,
inputOutV: outV,
label: EditorNeighborsComponent.DEFAULT_BLANK_VALUE
label: EditorNeighborsComponent.DEFAULT_BLANK_VALUE,
});
this.onUpdateEdges();
}
@@ -215,7 +215,7 @@ export class EditorNeighborsComponent extends React.Component<EditorNeighborsCom
</td>
<td className="actionCol">
<span className="rightPaneTrashIcon rightPaneBtns">
<img src={DeleteIcon} alt="Delete" onClick={e => this.removeAddedEdgeToNeighbor(index)} />
<img src={DeleteIcon} alt="Delete" onClick={(e) => this.removeAddedEdgeToNeighbor(index)} />
</span>
</td>
</tr>

View File

@@ -14,7 +14,7 @@ describe("<EditorNodePropertiesComponent />", () => {
readOnlyProperties: [
{
key: "singlevalueprop",
values: [{ value: "abcd", type: "string" }]
values: [{ value: "abcd", type: "string" }],
},
{
key: "multivaluesprop",
@@ -24,14 +24,14 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" },
{ value: false, type: "boolean" },
{ value: undefined, type: "null" },
{ value: null, type: "null" }
]
}
{ value: null, type: "null" },
],
},
],
existingProperties: [
{
key: "singlevalueprop2",
values: [{ value: "ijkl", type: "string" }]
values: [{ value: "ijkl", type: "string" }],
},
{
key: "multivaluesprop2",
@@ -41,14 +41,14 @@ describe("<EditorNodePropertiesComponent />", () => {
{ value: true, type: "boolean" },
{ value: false, type: "boolean" },
{ value: undefined, type: "null" },
{ value: null, type: "null" }
]
}
{ value: null, type: "null" },
],
},
],
addedProperties: [],
droppedKeys: []
droppedKeys: [],
},
onUpdateProperties: (editedProperties: EditedProperties): void => {}
onUpdateProperties: (editedProperties: EditedProperties): void => {},
};
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot();
@@ -61,27 +61,27 @@ describe("<EditorNodePropertiesComponent />", () => {
readOnlyProperties: [
{
key: "unicode1",
values: [{ value: "Véronique", type: "string" }]
values: [{ value: "Véronique", type: "string" }],
},
{
key: "unicode2",
values: [{ value: "亜妃子", type: "string" }]
}
values: [{ value: "亜妃子", type: "string" }],
},
],
existingProperties: [
{
key: "unicode1",
values: [{ value: "André", type: "string" }]
values: [{ value: "André", type: "string" }],
},
{
key: "unicode2",
values: [{ value: "あきら, アキラ,安喜良", type: "string" }]
}
values: [{ value: "あきら, アキラ,安喜良", type: "string" }],
},
],
addedProperties: [],
droppedKeys: []
droppedKeys: [],
},
onUpdateProperties: (editedProperties: EditedProperties): void => {}
onUpdateProperties: (editedProperties: EditedProperties): void => {},
};
const wrapper = shallow(<EditorNodePropertiesComponent {...props} />);
expect(wrapper).toMatchSnapshot();

View File

@@ -79,7 +79,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
{this.props.editedProperties.readOnlyProperties.map((nodeProp: ViewModels.InputProperty) =>
ReadOnlyNodePropertiesComponent.renderReadOnlyPropertyKeyPair(
nodeProp.key,
nodeProp.values.map(val => val.value)
nodeProp.values.map((val) => val.value)
)
)}
</React.Fragment>
@@ -112,7 +112,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
type="text"
value={singleValue.value.toString()}
placeholder="Value"
onChange={e => {
onChange={(e) => {
singleValue.value = e.target.value;
this.props.onUpdateProperties(this.props.editedProperties);
}}
@@ -123,7 +123,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
<select
className="typeSelect"
value={singleValue.type}
onChange={e => {
onChange={(e) => {
singleValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
if (singleValue.type === "null") {
singleValue.value = null;
@@ -144,7 +144,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns"
as="span"
aria-label="Delete property"
onActivated={e => this.removeExistingProperty(key)}
onActivated={(e) => this.removeExistingProperty(key)}
>
<img src={DeleteIcon} alt="Delete" />
</AccessibleElement>
@@ -157,14 +157,16 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
return (
<tr key={nodeProp.key}>
<td className="labelCol propertyId">{nodeProp.key}</td>
<td>{nodeProp.values.map(value => ReadOnlyNodePropertiesComponent.renderSinglePropertyValue(value.value))}</td>
<td>
{nodeProp.values.map((value) => ReadOnlyNodePropertiesComponent.renderSinglePropertyValue(value.value))}
</td>
<td />
<td className="actionCol">
<AccessibleElement
className="rightPaneTrashIcon rightPaneBtns"
as="span"
aria-label="Remove existing property"
onActivated={e => this.removeExistingProperty(nodeProp.key)}
onActivated={(e) => this.removeExistingProperty(nodeProp.key)}
>
<img src={DeleteIcon} alt="Delete" />
</AccessibleElement>
@@ -188,7 +190,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
type="text"
value={addedProperty.key}
placeholder="Key"
onChange={e => {
onChange={(e) => {
addedProperty.key = e.target.value;
this.props.onUpdateProperties(this.props.editedProperties);
}}
@@ -201,7 +203,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
type="text"
value={firstValue.value.toString()}
placeholder="Value"
onChange={e => {
onChange={(e) => {
firstValue.value = e.target.value;
if (firstValue.type === "null") {
firstValue.value = null;
@@ -215,7 +217,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
<select
className="typeSelect"
value={firstValue.type}
onChange={e => {
onChange={(e) => {
firstValue.type = e.target.value as ViewModels.InputPropertyValueTypeString;
this.props.onUpdateProperties(this.props.editedProperties);
}}
@@ -233,7 +235,7 @@ export class EditorNodePropertiesComponent extends React.Component<EditorNodePro
className="rightPaneTrashIcon rightPaneBtns"
as="span"
aria-label="Remove property"
onActivated={e => this.removeAddedProperty(index)}
onActivated={(e) => this.removeAddedProperty(index)}
>
<img src={DeleteIcon} alt="Delete" />
</AccessibleElement>

View File

@@ -69,8 +69,8 @@ describe("Graph Data", () => {
id: "id",
label: "label",
properties: {
testString: [{ id: "123", value: stringValue }]
}
testString: [{ id: "123", value: stringValue }],
},
},
"testString"
);
@@ -85,8 +85,8 @@ describe("Graph Data", () => {
id: "id",
label: "label",
properties: {
testString: [{ id: "123", value: numberValue }]
}
testString: [{ id: "123", value: numberValue }],
},
},
"testString"
);
@@ -101,8 +101,8 @@ describe("Graph Data", () => {
id: "id",
label: "label",
properties: {
testString: [{ id: "123", value: booleanValue }]
}
testString: [{ id: "123", value: booleanValue }],
},
},
"testString"
);

View File

@@ -152,7 +152,7 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
const v = this.getVertexById(e.outV);
GraphData.addOutE(v, p, {
id: e.id,
inV: e.outV
inV: e.outV,
});
}
});
@@ -165,7 +165,7 @@ export class GraphData<V extends GremlinVertex, E extends GremlinEdge> {
const v = this.getVertexById(e.inV);
GraphData.addInE(v, p, {
id: e.id,
outV: e.inV
outV: e.inV,
});
}
});

View File

@@ -52,8 +52,8 @@ describe("Check whether query result is edge-vertex array", () => {
GraphExplorer.isEdgeVertexPairArray([
{
e: { id: "ide", type: "edge" },
v: { id: "idv", type: "vertex" }
}
v: { id: "idv", type: "vertex" },
},
])
).toBe(true);
});
@@ -75,7 +75,7 @@ describe("getPkIdFromDocumentId", () => {
_self: "_self",
_etag: "_etag",
_ts: 1234,
...override
...override,
});
it("should create pkid pair from non-partitioned graph", () => {
@@ -171,7 +171,7 @@ describe("GraphExplorer", () => {
/* TODO Figure out how to make this Knockout-free */
graphConfigUiData: graphConfigUi,
graphConfig: graphConfig
graphConfig: graphConfig,
};
};
@@ -244,7 +244,7 @@ describe("GraphExplorer", () => {
selectNode: sinon.spy(),
resetZoom: sinon.spy(),
updateGraph: sinon.stub().callsFake(() => complete()),
enableHighlight: sinon.spy()
enableHighlight: sinon.spy(),
};
graphExplorer.d3ForceGraph = mockGraphRenderer;
});
@@ -268,7 +268,7 @@ describe("GraphExplorer", () => {
client.params.successCallback({
requestId: requestId,
data: backendResponse.response,
requestCharge: gremlinRU
requestCharge: gremlinRU,
});
if (backendResponse.isLast) {
@@ -305,7 +305,7 @@ describe("GraphExplorer", () => {
_query: query,
nextItem: (callback: (error: any, document: DataModels.DocumentId) => void): void => {},
hasMoreResults: () => false,
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {}
executeNext: (callback: (error: any, documents: DataModels.DocumentId[], headers: any) => void): void => {},
};
});
(queryDocumentsPage as jest.Mock).mockImplementation(
@@ -318,7 +318,7 @@ describe("GraphExplorer", () => {
documents: docDBResponse.response,
activityId: "",
headers: [] as any[],
requestCharge: gVRU
requestCharge: gVRU,
});
}
);
@@ -343,11 +343,11 @@ describe("GraphExplorer", () => {
});
describe("Load Graph button", () => {
beforeEach(async done => {
beforeEach(async (done) => {
const backendResponses: BackendResponses = {};
backendResponses["g.V()"] = backendResponses["g.V('1')"] = {
response: [{ id: "1", type: "vertex" }],
isLast: false
isLast: false,
};
backendResponses[createFetchOutEQuery("1", GraphExplorer.LOAD_PAGE_SIZE + 1)] = { response: [], isLast: false };
backendResponses[createFetchInEQuery("1", GraphExplorer.LOAD_PAGE_SIZE + 1)] = { response: [], isLast: true };
@@ -370,7 +370,7 @@ describe("GraphExplorer", () => {
it("should submit g.V() as docdb query with proper parameters", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery: true
enableCrossPartitionQuery: true,
});
});
@@ -380,11 +380,11 @@ describe("GraphExplorer", () => {
});
describe("Execute Gremlin Query button", () => {
beforeEach(done => {
beforeEach((done) => {
const backendResponses: BackendResponses = {};
backendResponses["g.V()"] = backendResponses["g.V('2')"] = {
response: [{ id: "2", type: "vertex" }],
isLast: false
isLast: false,
};
backendResponses[createFetchOutEQuery("2", GraphExplorer.LOAD_PAGE_SIZE + 1)] = { response: [], isLast: false };
backendResponses[createFetchInEQuery("2", GraphExplorer.LOAD_PAGE_SIZE + 1)] = { response: [], isLast: true };
@@ -407,7 +407,7 @@ describe("GraphExplorer", () => {
it("should submit g.V() as docdb query with proper parameters", () => {
expect(queryDocuments).toBeCalledWith("databaseId", "collectionId", DOCDB_G_DOT_V_QUERY, {
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery: true
enableCrossPartitionQuery: true,
});
});
@@ -435,10 +435,10 @@ describe("GraphExplorer", () => {
inV: node2Id,
outV: node1Id,
label: linkLabel,
type: "edge"
type: "edge",
};
beforeEach(done => {
beforeEach((done) => {
const backendResponses: BackendResponses = {};
// TODO Make this less dependent on spaces, order and quotes
backendResponses["g.V()"] = backendResponses[`g.V('${node1Id}','${node2Id}')`] = {
@@ -447,15 +447,15 @@ describe("GraphExplorer", () => {
id: node1Id,
label: label1,
type: "vertex",
properties: { prop1Id: [{ id: "id123", value: prop1Val1 }] }
properties: { prop1Id: [{ id: "id123", value: prop1Val1 }] },
},
{
id: node2Id,
label: label2,
type: "vertex"
}
type: "vertex",
},
],
isLast: false
isLast: false,
};
backendResponses[createFetchOutEQuery(node1Id, GraphExplorer.LOAD_PAGE_SIZE + 1)] = {
@@ -465,17 +465,17 @@ describe("GraphExplorer", () => {
v: {
id: node2Id,
label: label2,
type: "vertex"
}
}
type: "vertex",
},
},
],
isLast: false
isLast: false,
};
backendResponses[createFetchInEQuery(node1Id, GraphExplorer.LOAD_PAGE_SIZE)] = { response: [], isLast: true };
backendResponses[createFetchOutEQuery(node2Id, GraphExplorer.LOAD_PAGE_SIZE + 1)] = {
response: [],
isLast: false
isLast: false,
};
backendResponses[createFetchInEQuery(node2Id, GraphExplorer.LOAD_PAGE_SIZE + 1)] = {
response: [
@@ -485,16 +485,16 @@ describe("GraphExplorer", () => {
inV: node2Id,
outV: node1Id,
label: linkLabel,
type: "edge"
type: "edge",
},
v: {
id: node1Id,
label: label1,
type: "vertex"
}
}
type: "vertex",
},
},
],
isLast: true
isLast: true,
};
const docDBResponse: AjaxResponse = { response: [{ id: node1Id }, { id: node2Id }], isLast: false };
@@ -581,7 +581,7 @@ describe("GraphExplorer", () => {
describe("Select root node", () => {
let loadNeighborsPageStub: sinon.SinonSpy;
beforeEach(done => {
beforeEach((done) => {
loadNeighborsPageStub = sinon.stub(graphExplorerInstance, "loadNeighborsPage").callsFake(() => {
return Q.resolve();
});
@@ -660,12 +660,12 @@ describe("GraphExplorer", () => {
let processGremlinQueryResultsStub: sinon.SinonSpy;
let graphExplorerInstance: GraphExplorer;
beforeEach(done => {
beforeEach((done) => {
const backendResponses: BackendResponses = {};
// TODO Make this less dependent on spaces, order and quotes
backendResponses["g.V()"] = {
response: "invalid response",
isLast: true
isLast: true,
};
const docDBResponse: AjaxResponse = { response: [], isLast: false };
@@ -695,11 +695,11 @@ describe("GraphExplorer", () => {
});
describe("when isGraphAutoVizDisabled setting is true (autoviz disabled)", () => {
beforeEach(done => {
beforeEach((done) => {
const backendResponses: BackendResponses = {};
backendResponses["g.V()"] = backendResponses["g.V('3')"] = {
response: [{ id: "3", type: "vertex" }],
isLast: true
isLast: true,
};
const docDBResponse: AjaxResponse = { response: [{ id: "3" }], isLast: false };

View File

@@ -93,7 +93,7 @@ enum FilterQueryStatus {
GraphResult,
Loading,
NonGraphResult,
ErrorResult
ErrorResult,
}
interface GraphExplorerState {
@@ -159,7 +159,7 @@ enum ResultDisplay {
None,
Graph,
Json,
Stats
Stats,
}
interface UserQueryResult {
@@ -237,7 +237,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
resultDisplay: ResultDisplay.None,
filterQueryError: null,
filterQueryWarning: null,
filterQueryStatus: FilterQueryStatus.NoResult
filterQueryStatus: FilterQueryStatus.NoResult,
};
// Not part of React state
@@ -252,26 +252,26 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
title: "JSON",
content: {
className: "graphJsonEditor graphTabContent",
render: () => this.renderResultAsJson()
render: () => this.renderResultAsJson(),
},
isVisible: () => true
isVisible: () => true,
},
{
title: "Graph",
content: {
className: "graphTabContent",
render: () => this.renderResultAsGraph()
render: () => this.renderResultAsGraph(),
},
isVisible: () => this.state.filterQueryStatus === FilterQueryStatus.GraphResult
isVisible: () => this.state.filterQueryStatus === FilterQueryStatus.GraphResult,
},
{
title: GraphExplorer.QUERY_STATS_BUTTON_LABEL,
content: {
className: "graphTabContent",
render: () => this.renderResultStats()
render: () => this.renderResultStats(),
},
isVisible: () => true
}
isVisible: () => true,
},
];
this.queryRawData = null;
@@ -286,7 +286,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}
/* TODO Make this Knockout-free ! */
this.props.graphConfigUiData.nodeCaptionChoice.subscribe(key => {
this.props.graphConfigUiData.nodeCaptionChoice.subscribe((key) => {
this.props.graphConfig.nodeCaption(key);
const selectedNode = this.state.highlightedNode;
if (selectedNode) {
@@ -295,20 +295,20 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.render();
});
this.props.graphConfigUiData.nodeColorKeyChoice.subscribe(val => {
this.props.graphConfigUiData.nodeColorKeyChoice.subscribe((val) => {
this.props.graphConfig.nodeColorKey(val === GraphExplorer.NONE_CHOICE ? null : val);
this.render();
});
this.props.graphConfigUiData.showNeighborType.subscribe(val => {
this.props.graphConfigUiData.showNeighborType.subscribe((val) => {
this.props.graphConfig.showNeighborType(val);
this.render();
});
this.props.graphConfigUiData.nodeIconChoice.subscribe(val => {
this.props.graphConfigUiData.nodeIconChoice.subscribe((val) => {
this.updateNodeIcons(val, this.props.graphConfigUiData.nodeIconSet());
this.render();
});
this.props.graphConfigUiData.nodeIconSet.subscribe(val => {
this.props.graphConfigUiData.nodeIconSet.subscribe((val) => {
this.updateNodeIcons(this.props.graphConfigUiData.nodeIconChoice(), val);
this.render();
});
@@ -316,7 +316,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
props.onGraphAccessorCreated({
applyFilter: this.submitQuery.bind(this),
addVertex: this.addVertex.bind(this)
addVertex: this.addVertex.bind(this),
});
} // constructor
@@ -341,7 +341,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
let pkId = editedProperties.pkId;
let updateQueryFragment = "";
finalProperties.forEach(p => {
finalProperties.forEach((p) => {
// Partition key cannot be updated
if (p.key === partitionKeyProperty) {
return;
@@ -370,7 +370,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setNodePropertiesViewMode(NodeProperties.Mode.READONLY_PROP);
return Q.resolve({
data: [],
isIncomplete: false
isIncomplete: false,
});
}
@@ -390,7 +390,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
} else {
promise = Q.resolve({
data: [],
isIncomplete: false
isIncomplete: false,
});
}
@@ -683,8 +683,8 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
: null),
currentPage: {
start: offsetIndex,
end: offsetIndex + addedEdgesNb
}
end: offsetIndex + addedEdgesNb,
},
};
}
updateGraphData();
@@ -739,7 +739,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
enableCrossPartitionQuery:
StorageUtility.LocalStorageUtility.getEntryString(
StorageUtility.StorageKey.IsCrossPartitionQueryEnabled
) === "true"
) === "true",
} as FeedOptions
);
const response = await iterator.fetchNext();
@@ -878,7 +878,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setState({
rootMap: {},
hasMoreRoots: false,
selectedRootId: null
selectedRootId: null,
});
this.setFilterQueryStatus(FilterQueryStatus.Loading);
@@ -903,7 +903,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
filterQueryError: errorMsg,
});
}
}
@@ -1036,7 +1036,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
collectionName: this.props.collectionId,
defaultExperience: Constants.DefaultAccountExperience.Graph,
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Graph"
tabTitle: "Graph",
},
this.props.onLoadStartKey
);
@@ -1148,7 +1148,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
$.each(documents, (index: number, doc: any) => {
newIconsMap[doc["_graph_icon_property_value"]] = {
data: doc["icon"],
format: doc["format"]
format: doc["format"],
};
});
@@ -1172,7 +1172,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
this.setResultDisplay(ResultDisplay.None);
this.setState({
filterQueryError: null,
filterQueryWarning: null
filterQueryWarning: null,
});
break;
case FilterQueryStatus.NonGraphResult:
@@ -1206,7 +1206,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
let result = GraphData.GraphData.getNodePropValue(value, key);
return {
caption: result !== undefined ? result : value.id,
id: value.id
id: value.id,
};
}
);
@@ -1273,7 +1273,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
databaseId: this.props.databaseId,
collectionId: this.props.collectionId,
masterKey: this.props.masterKey,
maxResultSize: GraphExplorer.MAX_RESULT_SIZE
maxResultSize: GraphExplorer.MAX_RESULT_SIZE,
});
}
@@ -1298,7 +1298,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const isTabsContentExpanded = !this.state.isTabsContentExpanded;
this.setState({
isTabsContentExpanded: isTabsContentExpanded,
isPropertiesCollapsed: isTabsContentExpanded
isPropertiesCollapsed: isTabsContentExpanded,
});
}
@@ -1399,8 +1399,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
private updatePossibleVertices(): Promise<PossibleVertex[]> {
const highlightedNodeId = this.state.highlightedNode ? this.state.highlightedNode.id : null;
const q = `SELECT c.id, c["${this.props.graphConfigUiData.nodeCaptionChoice() ||
"id"}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
const q = `SELECT c.id, c["${
this.props.graphConfigUiData.nodeCaptionChoice() || "id"
}"] AS p FROM c WHERE NOT IS_DEFINED(c._isEdge)`;
return this.executeNonPagedDocDbQuery(q).then(
(documents: DataModels.DocumentId[]) => {
let possibleVertices = [] as PossibleVertex[];
@@ -1414,13 +1415,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
if (typeof item.p === "string" || item.p instanceof String) {
possibleVertices.push({
value: item.id,
caption: item.p
caption: item.p,
});
} else {
if (item.hasOwnProperty("p")) {
possibleVertices.push({
value: item.id,
caption: item.p[0]["_value"]
caption: item.p[0]["_value"],
});
}
}
@@ -1548,7 +1549,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
// A bit of translation to make it easier to display
let props: { [id: string]: ViewModels.GremlinPropertyValueType[] } = {};
for (let p in data.properties) {
props[p] = data.properties[p].map(gremlinProperty => gremlinProperty.value);
props[p] = data.properties[p].map((gremlinProperty) => gremlinProperty.value);
}
// update neighbors
@@ -1564,7 +1565,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
properties: props,
areNeighborsUnknown: !data._inEdgeIds || !data._outEdgeIds,
sources: sources, //<VertexBasicInfo[]>[],
targets: targets //<VertexBasicInfo[]>[]
targets: targets, //<VertexBasicInfo[]>[]
};
// Update KO
@@ -1627,7 +1628,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
(value: string, index: number, array: string[]): InputTypeaheadComponent.Item => {
return { caption: value, value: value };
}
)
),
});
}
@@ -1687,11 +1688,11 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
onHighlightedNode: this.onHighlightedNode.bind(this),
onLoadMoreData: this.onLoadMoreData.bind(this),
onInitialized: (instance: D3ForceGraph.GraphRenderer): void => this.onMiddlePaneInitialized(instance),
onGraphUpdated: this.onGraphUpdated.bind(this)
onGraphUpdated: this.onGraphUpdated.bind(this),
};
const graphVizProp: GraphVizComponentProps = {
forceGraphParams: forceGraphParams
forceGraphParams: forceGraphParams,
};
return (
@@ -1741,13 +1742,13 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
{
maxItemCount: GraphExplorer.ROOT_LIST_PAGE_SIZE,
enableCrossPartitionQuery:
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true"
LocalStorageUtility.getEntryString(StorageKey.IsCrossPartitionQueryEnabled) === "true",
} as FeedOptions
);
this.currentDocDBQueryInfo = {
iterator: iterator,
index: 0,
query: query
query: query,
};
return await this.loadMoreRootNodes();
} catch (error) {
@@ -1765,8 +1766,9 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
}
let RU: string = GraphExplorer.REQUEST_CHARGE_UNKNOWN_MSG;
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${this
.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE})`;
const queryInfoStr = `${this.currentDocDBQueryInfo.query} (${this.currentDocDBQueryInfo.index + 1}-${
this.currentDocDBQueryInfo.index + GraphExplorer.ROOT_LIST_PAGE_SIZE
})`;
const id = GraphExplorer.reportToConsole(ConsoleDataType.InProgress, `Executing: ${queryInfoStr}`);
try {
@@ -1797,7 +1799,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const errorMsg = `Failed to query: ${this.currentDocDBQueryInfo.query}. Reason:${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
filterQueryError: errorMsg,
});
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
throw error;
@@ -1819,7 +1821,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const errorMsg = `Failed to execute query: ${query}: ${error}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
filterQueryError: errorMsg,
});
this.setFilterQueryStatus(FilterQueryStatus.ErrorResult);
throw error;
@@ -1832,7 +1834,7 @@ export class GraphExplorer extends React.Component<GraphExplorerProps, GraphExpl
const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
filterQueryError: errorMsg,
});
});

View File

@@ -20,8 +20,8 @@ describe("Process Gremlin vertex", () => {
id: "id",
label: "label",
inE: {
inEdge: [{ id: "id1", outV: "outV1" }]
}
inEdge: [{ id: "id1", outV: "outV1" }],
},
};
GraphUtil.createEdgesfromNode(v, graphData);
const expectedEdge: GremlinEdge = { id: "id1", inV: "id", outV: "outV1", label: "inEdge" };
@@ -33,8 +33,8 @@ describe("Process Gremlin vertex", () => {
id: "id",
label: "label",
outE: {
outEdge: [{ id: "id2", inV: "inV2" }]
}
outEdge: [{ id: "id2", inV: "inV2" }],
},
};
GraphUtil.createEdgesfromNode(v, graphData);
const expectedEdge: GremlinEdge = { id: "id2", inV: "inV2", outV: "id", label: "outEdge" };
@@ -47,14 +47,14 @@ describe("Process Gremlin vertex", () => {
id: "id",
label: "label",
inE: {
inEdge: [{ id: "id1", outV: "outV1" }]
inEdge: [{ id: "id1", outV: "outV1" }],
},
outE: {
outEdge: [
{ id: "id2", inV: "inV2" },
{ id: "id3", inV: "inV3" }
]
}
{ id: "id3", inV: "inV3" },
],
},
};
const newNodes = {};
GraphUtil.createEdgesfromNode(v, graphData, newNodes);
@@ -83,7 +83,7 @@ describe("getLimitedArrayString()", () => {
it("should handle nth element makes it exceed max limit", () => {
const expected = {
result: "'1','2'",
consumedCount: 2
consumedCount: 2,
};
expect(GraphUtil.getLimitedArrayString(["1", "2", "12345", "4", "5"], 10)).toEqual(expected);
});
@@ -91,7 +91,7 @@ describe("getLimitedArrayString()", () => {
it("should consume all elements if limit never exceeding limit", () => {
const expected = {
result: "'1','22','3'",
consumedCount: 3
consumedCount: 3,
};
expect(GraphUtil.getLimitedArrayString(["1", "22", "3"], 12)).toEqual(expected);
});

Some files were not shown because too many files have changed in this diff Show More