mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-20 17:30:46 +00:00
Migrate graph style panel to react (#619)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
import * as ko from "knockout";
|
||||
import { GraphStyleComponent, GraphStyleParams } from "./GraphStyleComponent";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
|
||||
function buildComponent(buttonOptions: any) {
|
||||
document.body.innerHTML = GraphStyleComponent.template as any;
|
||||
const vm = new GraphStyleComponent.viewModel(buttonOptions);
|
||||
ko.applyBindings(vm);
|
||||
}
|
||||
|
||||
describe("Graph Style Component", () => {
|
||||
let buildParams = (config: ViewModels.GraphConfigUiData): GraphStyleParams => {
|
||||
return {
|
||||
config: config,
|
||||
};
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
ko.cleanNode(document);
|
||||
});
|
||||
|
||||
describe("Rendering", () => {
|
||||
it("should display proper list of choices passed in component parameters", () => {
|
||||
const PROP2 = "prop2";
|
||||
const PROPC = "prop3";
|
||||
const params = buildParams({
|
||||
nodeCaptionChoice: ko.observable(null),
|
||||
nodeIconChoice: ko.observable(null),
|
||||
nodeColorKeyChoice: ko.observable(null),
|
||||
nodeIconSet: ko.observable(null),
|
||||
nodeProperties: ko.observableArray(["prop1", PROP2]),
|
||||
nodePropertiesWithNone: ko.observableArray(["propa", "propb", PROPC]),
|
||||
showNeighborType: ko.observable(null),
|
||||
});
|
||||
|
||||
buildComponent(params);
|
||||
|
||||
var e: any = document.querySelector(".graphStyle #nodeCaptionChoices");
|
||||
expect(e.options.length).toBe(2);
|
||||
expect(e.options[1].value).toBe(PROP2);
|
||||
|
||||
e = document.querySelector(".graphStyle #nodeColorKeyChoices");
|
||||
expect(e.options.length).toBe(3);
|
||||
expect(e.options[2].value).toBe(PROPC);
|
||||
|
||||
e = document.querySelector(".graphStyle #nodeIconChoices");
|
||||
expect(e.options.length).toBe(3);
|
||||
expect(e.options[2].value).toBe(PROPC);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { IGraphConfig } from "../../Tabs/GraphTab";
|
||||
import { GraphStyleComponent, GraphStyleProps } from "./GraphStyleComponent";
|
||||
|
||||
describe("Graph Style Component", () => {
|
||||
let fakeGraphConfig: IGraphConfig;
|
||||
let fakeGraphConfigUiData: ViewModels.IGraphConfigUiData;
|
||||
let props: GraphStyleProps;
|
||||
beforeEach(() => {
|
||||
fakeGraphConfig = {
|
||||
nodeColor: "orange",
|
||||
nodeColorKey: "node2",
|
||||
linkColor: "orange",
|
||||
showNeighborType: 0,
|
||||
nodeCaption: "node1",
|
||||
nodeSize: 10,
|
||||
linkWidth: 1,
|
||||
nodeIconKey: undefined,
|
||||
iconsMap: {},
|
||||
};
|
||||
fakeGraphConfigUiData = {
|
||||
nodeCaptionChoice: "node1",
|
||||
nodeIconChoice: undefined,
|
||||
nodeColorKeyChoice: "node2",
|
||||
nodeIconSet: undefined,
|
||||
nodeProperties: ["node1", "node2", "node3"],
|
||||
nodePropertiesWithNone: ["none", "node1", "node2", "node3"],
|
||||
showNeighborType: undefined,
|
||||
};
|
||||
props = {
|
||||
igraphConfig: fakeGraphConfig,
|
||||
igraphConfigUiData: fakeGraphConfigUiData,
|
||||
getValues: (): void => undefined,
|
||||
};
|
||||
|
||||
render(<GraphStyleComponent {...props} />);
|
||||
});
|
||||
|
||||
it("should render default property", () => {
|
||||
const { asFragment } = render(<GraphStyleComponent {...props} />);
|
||||
expect(asFragment).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render node properties dropdown list ", () => {
|
||||
const dropDownList = screen.getByText("Show vertex (node) as");
|
||||
expect(dropDownList).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render Map this property to node color dropdown list", () => {
|
||||
const nodeColorDropdownList = screen.getByText("Map this property to node color");
|
||||
expect(nodeColorDropdownList).toBeDefined();
|
||||
});
|
||||
|
||||
it("should render show neighbor options", () => {
|
||||
const nodeShowNeighborOptions = screen.getByText("Show");
|
||||
expect(nodeShowNeighborOptions).toBeDefined();
|
||||
});
|
||||
|
||||
it("should call handleOnChange method", () => {
|
||||
const handleOnChange = jest.fn();
|
||||
const nodeCaptionDropdownList = screen.getByText("Show vertex (node) as");
|
||||
nodeCaptionDropdownList.onchange = handleOnChange();
|
||||
expect(handleOnChange).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,103 +0,0 @@
|
||||
import * as Constants from "../../../Common/Constants";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { WaitsForTemplateViewModel } from "../../WaitsForTemplateViewModel";
|
||||
|
||||
/**
|
||||
* Parameters for this component
|
||||
*/
|
||||
export interface GraphStyleParams {
|
||||
config: ViewModels.GraphConfigUiData;
|
||||
firstFieldHasFocus?: ko.Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Callback triggered when the template is bound to the component (for testing purposes)
|
||||
*/
|
||||
onTemplateReady?: () => void;
|
||||
}
|
||||
|
||||
class GraphStyleViewModel extends WaitsForTemplateViewModel {
|
||||
private params: GraphStyleParams;
|
||||
|
||||
public constructor(params: GraphStyleParams) {
|
||||
super();
|
||||
super.onTemplateReady((isTemplateReady: boolean) => {
|
||||
if (isTemplateReady && params.onTemplateReady) {
|
||||
params.onTemplateReady();
|
||||
}
|
||||
});
|
||||
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public onAllNeighborsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.params.config.showNeighborType(ViewModels.NeighborType.BOTH);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onSourcesKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.params.config.showNeighborType(ViewModels.NeighborType.SOURCES_ONLY);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public onTargetsKeyPress = (source: any, event: KeyboardEvent): boolean => {
|
||||
if (event.keyCode === Constants.KeyCodes.Space || event.keyCode === Constants.KeyCodes.Enter) {
|
||||
this.params.config.showNeighborType(ViewModels.NeighborType.TARGETS_ONLY);
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
const template = `
|
||||
<div id="graphStyle" class="graphStyle" data-bind="setTemplateReady: true, with:params.config">
|
||||
<div class="seconddivpadding">
|
||||
<p>Show vertex (node) as</p>
|
||||
<select id="nodeCaptionChoices" class="formTree paneselect" required data-bind="options:nodeProperties,
|
||||
value:nodeCaptionChoice, hasFocus: $parent.params.firstFieldHasFocus"></select>
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
<p>Map this property to node color</p>
|
||||
<select id="nodeColorKeyChoices" class="formTree paneselect" required data-bind="options:nodePropertiesWithNone,
|
||||
value:nodeColorKeyChoice"></select>
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
<p>Map this property to node icon</p>
|
||||
<select id="nodeIconChoices" class="formTree paneselect" required data-bind="options:nodePropertiesWithNone,
|
||||
value:nodeIconChoice"></select>
|
||||
<input type="text" data-bind="value:nodeIconSet" placeholder="Icon set: blank for collection id" class="nodeIconSet" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<p class="seconddivpadding">Show</p>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab">
|
||||
<input type="radio" id="tab11" name="graphneighbortype" class="radio" data-bind="checkedValue:2, checked:showNeighborType" />
|
||||
<label for="tab11" tabindex="0" data-bind="event: { keypress: $parent.onAllNeighborsKeyPress }">All neighbors</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input type="radio" id="tab12" name="graphneighbortype" class="radio" data-bind="checkedValue:0, checked:showNeighborType" />
|
||||
<label for="tab12" tabindex="0" data-bind="event: { keypress: $parent.onSourcesKeyPress }">Sources</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input type="radio" id="tab13" name="graphneighbortype" class="radio" data-bind="checkedValue:1, checked:showNeighborType" />
|
||||
<label for="tab13" tabindex="0" data-bind="event: { keypress: $parent.onTargetsKeyPress }">Targets</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export const GraphStyleComponent = {
|
||||
viewModel: GraphStyleViewModel,
|
||||
template,
|
||||
};
|
||||
131
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.tsx
Normal file
131
src/Explorer/Graph/GraphStyleComponent/GraphStyleComponent.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { ChoiceGroup, Dropdown, IChoiceGroupOption, IDropdownOption, IDropdownStyles, Stack } from "@fluentui/react";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import { IGraphConfigUiData, NeighborType } from "../../../Contracts/ViewModels";
|
||||
import { IGraphConfig } from "../../Tabs/GraphTab";
|
||||
const IGraphConfigType = {
|
||||
NODE_CAPTION: "NODE_CAPTION",
|
||||
NODE_COLOR: "NODE_COLOR",
|
||||
NODE_ICON: "NODE_ICON",
|
||||
SHOW_NEIGHBOR_TYPE: "SHOW_NEIGHBOR_TYPE",
|
||||
};
|
||||
export interface GraphStyleProps {
|
||||
igraphConfig: IGraphConfig;
|
||||
igraphConfigUiData: IGraphConfigUiData;
|
||||
getValues: (igraphConfig?: IGraphConfig) => void;
|
||||
}
|
||||
|
||||
export const GraphStyleComponent: FunctionComponent<GraphStyleProps> = ({
|
||||
igraphConfig,
|
||||
igraphConfigUiData,
|
||||
getValues,
|
||||
}: GraphStyleProps): JSX.Element => {
|
||||
const [igraphConfigState, setIGraphConfig] = useState<IGraphConfig>(igraphConfig);
|
||||
const [selected, setSelected] = useState<boolean>(false);
|
||||
|
||||
const nodePropertiesOptions = igraphConfigUiData.nodeProperties.map((nodeProperty) => ({
|
||||
key: nodeProperty,
|
||||
text: nodeProperty,
|
||||
}));
|
||||
|
||||
const nodePropertiesWithNoneOptions = igraphConfigUiData.nodePropertiesWithNone.map((nodePropertyWithNone) => ({
|
||||
key: nodePropertyWithNone,
|
||||
text: nodePropertyWithNone,
|
||||
}));
|
||||
|
||||
const showNeighborTypeOptions: IChoiceGroupOption[] = [
|
||||
{ key: NeighborType.BOTH.toString(), text: "All neighbors" },
|
||||
{ key: NeighborType.SOURCES_ONLY.toString(), text: "Sources" },
|
||||
{ key: NeighborType.TARGETS_ONLY.toString(), text: "Targets" },
|
||||
];
|
||||
|
||||
const dropdownStyles: Partial<IDropdownStyles> = {
|
||||
dropdown: { height: 32, marginRight: 10 },
|
||||
};
|
||||
const choiceButtonStyles = {
|
||||
flexContainer: [
|
||||
{
|
||||
selectors: {
|
||||
".ms-ChoiceField-wrapper label": {
|
||||
fontSize: 14,
|
||||
paddingTop: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selected) {
|
||||
getValues(igraphConfigState);
|
||||
}
|
||||
//eslint-disable-next-line
|
||||
}, [igraphConfigState]);
|
||||
|
||||
const handleOnChange = (val: string, igraphConfigType: string) => {
|
||||
switch (igraphConfigType) {
|
||||
case IGraphConfigType.NODE_CAPTION:
|
||||
setSelected(true);
|
||||
setIGraphConfig({
|
||||
...igraphConfigState,
|
||||
nodeCaption: val,
|
||||
});
|
||||
break;
|
||||
case IGraphConfigType.NODE_COLOR:
|
||||
setSelected(true);
|
||||
setIGraphConfig({
|
||||
...igraphConfigState,
|
||||
nodeColorKey: val,
|
||||
});
|
||||
break;
|
||||
case IGraphConfigType.SHOW_NEIGHBOR_TYPE:
|
||||
setSelected(true);
|
||||
setIGraphConfig({
|
||||
...igraphConfigState,
|
||||
showNeighborType: parseInt(val),
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Stack>
|
||||
<div id="graphStyle" className="graphStyle">
|
||||
<div className="seconddivpadding">
|
||||
<Dropdown
|
||||
label="Show vertex (node) as"
|
||||
options={nodePropertiesOptions}
|
||||
required
|
||||
selectedKey={igraphConfigState.nodeCaption}
|
||||
styles={dropdownStyles}
|
||||
onChange={(_, options: IDropdownOption) =>
|
||||
handleOnChange(options.key.toString(), IGraphConfigType.NODE_CAPTION)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="seconddivpadding">
|
||||
<Dropdown
|
||||
label="Map this property to node color"
|
||||
options={nodePropertiesWithNoneOptions}
|
||||
required
|
||||
selectedKey={igraphConfigState.nodeColorKey}
|
||||
styles={dropdownStyles}
|
||||
onChange={(_, options: IDropdownOption) =>
|
||||
handleOnChange(options.key.toString(), IGraphConfigType.NODE_COLOR)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="seconddivpadding">
|
||||
<ChoiceGroup
|
||||
label="Show"
|
||||
styles={choiceButtonStyles}
|
||||
options={showNeighborTypeOptions}
|
||||
selectedKey={igraphConfigState.showNeighborType.toString()}
|
||||
onChange={(_, options: IChoiceGroupOption) =>
|
||||
handleOnChange(options.key.toString(), IGraphConfigType.SHOW_NEIGHBOR_TYPE)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Graph Style Component should render default property 1`] = `[Function]`;
|
||||
@@ -1,74 +0,0 @@
|
||||
<div id="graphStyle" class="graphStyle" data-bind="setTemplateReady: true, with:params.config">
|
||||
<div class="seconddivpadding">
|
||||
<p>Show vertex (node) as</p>
|
||||
<select
|
||||
id="nodeCaptionChoices"
|
||||
class="formTree paneselect"
|
||||
required
|
||||
data-bind="options:nodeProperties,
|
||||
value:nodeCaptionChoice, hasFocus: $parent.params.firstFieldHasFocus"
|
||||
></select>
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
<p>Map this property to node color</p>
|
||||
<select
|
||||
id="nodeColorKeyChoices"
|
||||
class="formTree paneselect"
|
||||
required
|
||||
data-bind="options:nodePropertiesWithNone,
|
||||
value:nodeColorKeyChoice"
|
||||
></select>
|
||||
</div>
|
||||
<div class="seconddivpadding">
|
||||
<p>Map this property to node icon</p>
|
||||
<select
|
||||
id="nodeIconChoices"
|
||||
class="formTree paneselect"
|
||||
required
|
||||
data-bind="options:nodePropertiesWithNone,
|
||||
value:nodeIconChoice"
|
||||
></select>
|
||||
<input
|
||||
type="text"
|
||||
data-bind="value:nodeIconSet"
|
||||
placeholder="Icon set: blank for collection id"
|
||||
class="nodeIconSet"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p class="seconddivpadding">Show</p>
|
||||
|
||||
<div class="tabs">
|
||||
<div class="tab">
|
||||
<input
|
||||
type="radio"
|
||||
id="tab11"
|
||||
name="graphneighbortype"
|
||||
class="radio"
|
||||
data-bind="checkedValue:2, checked:showNeighborType"
|
||||
/>
|
||||
<label for="tab11">All neighbors</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input
|
||||
type="radio"
|
||||
id="tab12"
|
||||
name="graphneighbortype"
|
||||
class="radio"
|
||||
data-bind="checkedValue:0, checked:showNeighborType"
|
||||
/>
|
||||
<label for="tab12">Sources</label>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<input
|
||||
type="radio"
|
||||
id="tab13"
|
||||
name="graphneighbortype"
|
||||
class="radio"
|
||||
data-bind="checkedValue:1, checked:showNeighborType"
|
||||
/>
|
||||
<label for="tab13">Targets</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user