Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameter Selector Display option #274

Merged
merged 8 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cypress/integration/start_page.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ describe('NeoDash E2E Tests', () => {

it('creates a single value report', () => {
createReportOfType('Single Value', barChartCypherQuery)
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root > div > div:nth-child(2) > span').contains('1,999')
cy.get('main .react-grid-item:eq(2) .MuiCardContent-root > div > div:nth-child(2) > span').invoke('text').then(text => {
expect(text).to.be.oneOf(['1999', '1,999'])
nielsdejong marked this conversation as resolved.
Show resolved Hide resolved
})
})

it('creates a gauge chart report', () => {
Expand Down
59 changes: 47 additions & 12 deletions src/card/settings/custom/CardSettingsContentPropertySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, settings, extens
const [labelInputText, setLabelInputText] = React.useState(settings['entityType']);
const [labelRecords, setLabelRecords] = React.useState([]);
const [propertyInputText, setPropertyInputText] = React.useState(settings['propertyType']);
const [propertyInputDisplayText, setPropertyInputDisplayText] = React.useState(settings['propertyDisplay']);
const [propertyRecords, setPropertyRecords] = React.useState([]);
var parameterName = settings['parameterName'];

// When certain settings are updated, a re-generated search query is needed.
useEffect(() => {
updateReportQuery(settings.entityType, settings.propertyType);
updateReportQuery(settings.entityType, settings.propertyType, settings.propertyDisplay);
}, [settings.suggestionLimit, settings.deduplicateSuggestions, settings.searchType, settings.caseSensitive])

if (settings["type"] == undefined) {
Expand All @@ -52,31 +53,44 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, settings, extens
onReportSettingUpdate('propertyType', undefined);
onReportSettingUpdate('id', undefined);
onReportSettingUpdate('parameterName', undefined);
onReportSettingUpdate('propertyDisplay', undefined);
onReportSettingUpdate("type", newValue);
}

function handleNodeLabelSelectionUpdate(newValue) {
setPropertyInputText("");
setPropertyInputDisplayText("");
onReportSettingUpdate('entityType', newValue);
onReportSettingUpdate('propertyType', undefined);
onReportSettingUpdate('parameterName', undefined);
onReportSettingUpdate('propertyDisplay', undefined);
}

function handleFreeTextNameSelectionUpdate(newValue) {
if (newValue) {
const new_parameter_name = ("neodash_" + newValue).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, newValue, undefined);
handleReportQueryUpdate(new_parameter_name, newValue, undefined, undefined);
} else {
onReportSettingUpdate('parameterName', undefined);
}
}

function handlePropertyNameSelectionUpdate(newValue) {
onReportSettingUpdate('propertyType', newValue);
onReportSettingUpdate("propertyType", newValue);
onReportSettingUpdate("propertyDisplay", newValue);
BennuFire marked this conversation as resolved.
Show resolved Hide resolved
if (newValue && settings['entityType']) {
const id = settings['id'] ? settings['id'] : "";
const new_parameter_name = "neodash_" + (settings['entityType'] + "_" + newValue + (id == "" || id.startsWith("_") ? id : "_" + id)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, settings['entityType'], newValue);
handleReportQueryUpdate(new_parameter_name, settings['entityType'], newValue, newValue);
} else {
onReportSettingUpdate('parameterName', undefined);
}
}

function handlePropertyDisplayNameSelectionUpdate(newValue) {
onReportSettingUpdate("propertyDisplay", newValue);
if (newValue && settings['entityType']) {
updateReportQuery(settings['entityType'], settings['propertyType'], newValue);
} else {
onReportSettingUpdate('parameterName', undefined);
}
Expand All @@ -88,32 +102,34 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, settings, extens
if (settings['propertyType'] && settings['entityType']) {
const id = value ? "_" + value : "";
const new_parameter_name = "neodash_" + (settings['entityType'] + "_" + settings['propertyType'] + (id == "" || id.startsWith("_") ? id : "_" + id)).toLowerCase().replaceAll(" ", "_").replaceAll("-", "_");
handleReportQueryUpdate(new_parameter_name, settings['entityType'], settings['propertyType']);
handleReportQueryUpdate(new_parameter_name, settings['entityType'], settings['propertyType'], settings['propertyDisplay']);
}
}

function handleReportQueryUpdate(new_parameter_name, entityType, propertyType) {
function handleReportQueryUpdate(new_parameter_name, entityType, propertyType, propertyDisplay) {
onReportSettingUpdate('parameterName', new_parameter_name);
updateReportQuery(entityType, propertyType);
updateReportQuery(entityType, propertyType, propertyDisplay);
}

function updateReportQuery(entityType, propertyType) {
function updateReportQuery(entityType, propertyType, propertyDisplay) {
const limit = settings.suggestionLimit ? settings.suggestionLimit : 5;
const deduplicate = settings.deduplicateSuggestions !== undefined ? settings.deduplicateSuggestions : true;
const searchType = settings.searchType ? settings.searchType : "CONTAINS";
const caseSensitive = settings.caseSensitive !== undefined ? settings.caseSensitive : false;
if (settings['type'] == "Node Property") {
const newQuery = "MATCH (n:`" + entityType + "`) \n"+
"WHERE "+(caseSensitive ? "" : "toLower")+"(toString(n.`" + propertyType + "`)) "+searchType+
"WHERE "+(caseSensitive ? "" : "toLower")+"(toString(n.`" + propertyDisplay + "`)) "+searchType+
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move the ternary caseSensitive ? "" : "toLower" out of the string and to place it in a const on line 118.5 in order to be able to reuse it on line 129

We can also directly replace the line 118 by something like: const toLowerOrEmptyString = (settings.caseSensitive || false) ? "" : "toLower"; or maybe in two lines for more readability, one with the caseSensitive const definition and the second to define the "toLower" const

What do you think of the template string style with ${} ? Can maybe be more readable than the "" + "" synthax but not sure here

" "+(caseSensitive ? "" : "toLower")+"($input) \n"+
"RETURN " + (deduplicate ? "DISTINCT" : "") + " n.`" + propertyType + "` as value "+
"RETURN " + (deduplicate ? "DISTINCT" : "") + " n.`" + propertyType + "` as value, "+
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem for this DISTINCT and the fact of reusing it on line 131

" n.`" + propertyDisplay + "` as display "+
"ORDER BY size(toString(value)) ASC LIMIT " + limit;
onQueryUpdate(newQuery);
} else if (settings['type'] == "Relationship Property") {
const newQuery = "MATCH ()-[n:`" + entityType + "`]->() \n"+
"WHERE "+(caseSensitive ? "" : "toLower")+"(toString(n.`" + propertyType + "`)) "+searchType+
"WHERE "+(caseSensitive ? "" : "toLower")+"(toString(n.`" + propertyDisplay + "`)) "+searchType+
" "+(caseSensitive ? "" : "toLower")+"($input) \n"+
"RETURN " + (deduplicate ? "DISTINCT" : "") + " n.`" + propertyType + "` as value "+
"RETURN " + (deduplicate ? "DISTINCT" : "") + " n.`" + propertyType + "` as value, "+
" n.`" + propertyDisplay + "` as display "+
"ORDER BY size(toString(value)) ASC LIMIT " + limit;
onQueryUpdate(newQuery);
} else {
Expand Down Expand Up @@ -192,6 +208,7 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, settings, extens
inputValue={propertyInputText}
onInputChange={(event, value) => {
setPropertyInputText(value);
setPropertyInputDisplayText(value);
if (manualPropertyNameSpecification) {
handlePropertyNameSelectionUpdate(value);
} else {
Expand All @@ -202,6 +219,24 @@ const NeoCardSettingsContentPropertySelect = ({ type, database, settings, extens
onChange={(event, newValue) => handlePropertyNameSelectionUpdate(newValue)}
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Property Name"} />}
/>
<Autocomplete
id="autocomplete-property-display"
options={manualPropertyNameSpecification ? [settings['propertyDisplay']] : propertyRecords.map(r => r["_fields"] ? r["_fields"][0] : "(no data)")}
getOptionLabel={(option) => option ? option : ""}
BennuFire marked this conversation as resolved.
Show resolved Hide resolved
style={{ display: "inline-block", width: 185, marginLeft: "5px", marginTop: "5px" }}
inputValue={propertyInputDisplayText}
onInputChange={(event, value) => {
setPropertyInputDisplayText(value);
if (manualPropertyNameSpecification) {
handlePropertyDisplayNameSelectionUpdate(value);
} else {
queryCallback("CALL db.propertyKeys() YIELD propertyKey as propertyName WITH propertyName WHERE toLower(propertyName) CONTAINS toLower($input) RETURN DISTINCT propertyName LIMIT 5", { input: value }, setPropertyRecords);
}
}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion this code can be moved in a dedicated function in order to have a more readable component, what do you think ?

value={settings['propertyDisplay']}
onChange={(event, newValue) => handlePropertyDisplayNameSelectionUpdate(newValue)}
renderInput={(params) => <TextField {...params} placeholder="Start typing..." InputLabelProps={{ shrink: true }} label={"Property Display"} />}
/>
<NeoField placeholder='number'
label="Number (optional)" disabled={!settings['propertyType']} value={settings['id']}
style={{ width: "135px", marginTop: "5px", marginLeft: "10px" }}
Expand Down
37 changes: 23 additions & 14 deletions src/chart/parameter/ParameterSelectionChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ const NeoParameterSelectionChart = (props: ChartProps) => {
const records = props.records;
const query = records[0]["input"] ? records[0]["input"] : undefined;
const parameter = props.settings && props.settings["parameterName"] ? props.settings["parameterName"] : undefined;
const parameterDisplay = parameter + "_display";
const type = props.settings && props.settings["type"] ? props.settings["type"] : undefined;
const suggestionsUpdateTimeout = props.settings && props.settings["suggestionsUpdateTimeout"] ? props.settings["suggestionsUpdateTimeout"] : 250;
const setParameterTimeout = props.settings && props.settings["setParameterTimeout"] ? props.settings["setParameterTimeout"] : 1000;
const defaultValue = props.settings && props.settings["defaultValue"] && props.settings["defaultValue"].length > 0 ? props.settings["defaultValue"] : "";
const currentValue = (props.getGlobalParameter && props.getGlobalParameter(parameter)) ? props.getGlobalParameter(parameter) : "";
const currentValue = (props.getGlobalParameter && props.getGlobalParameter(parameterDisplay)) ? props.getGlobalParameter(parameterDisplay) : "";
const [extraRecords, setExtraRecords] = React.useState([]);
const [inputText, setInputText] = React.useState(currentValue);
const queryCallback = props.queryCallback ? props.queryCallback : () => { };
Expand Down Expand Up @@ -48,18 +49,19 @@ const NeoParameterSelectionChart = (props: ChartProps) => {


// In case the components gets (re)loaded with a different/non-existing selected parameter, set the text to the current global parameter value.
if (query && value != currentValue && currentValue != inputText) {
/*if (query && value != currentValue && currentValue != inputText) {
setValue(currentValue);
setInputText(value == defaultValue ? "" : currentValue);
setExtraRecords([]);
}
}*/
nielsdejong marked this conversation as resolved.
Show resolved Hide resolved

if (!query || query.trim().length == 0) {
return <p style={{ margin: "15px" }}>No selection specified. Open up the report settings and choose a node label and property.</p>
}

const label = props.settings && props.settings["entityType"] ? props.settings["entityType"] : "";
const property = props.settings && props.settings["propertyType"] ? props.settings["propertyType"] : "";
const propertyDisplay = props.settings && props.settings["propertyDisplay"] ? props.settings["propertyDisplay"] : "";
BennuFire marked this conversation as resolved.
Show resolved Hide resolved
const settings = (props.settings) ? props.settings : {};
const helperText = settings.helperText;
const clearParameterOnFieldClear = settings.clearParameterOnFieldClear;
Expand All @@ -70,7 +72,7 @@ const NeoParameterSelectionChart = (props: ChartProps) => {
<div style={{ width: "100%" }}>
<NeoField
key={"freetext"}
label={helperText ? helperText : label + " " + property}
label={helperText ? helperText : label + " " + propertyDisplay}
BennuFire marked this conversation as resolved.
Show resolved Hide resolved
defaultValue={defaultValue}
value={value}
variant="outlined"
Expand All @@ -90,33 +92,40 @@ const NeoParameterSelectionChart = (props: ChartProps) => {
:
<Autocomplete
id="autocomplete"
options={extraRecords.map(r => r["_fields"] && r["_fields"][0] !== null ? r["_fields"][0] : "(no data)").sort()}
options={
extraRecords.map(r => r["_fields"] && r["_fields"][1] !== null ? r["_fields"][1] : "(no data)")
.sort()
}
getOptionLabel={(option) => option ? option.toString() : ""}
style={{ maxWidth: "calc(100% - 30px)", marginLeft: "15px", marginTop: "5px" }}
inputValue={inputText}
onInputChange={(event, value) => {
setInputText("" + value);
debouncedQueryCallback(query, { input: "" + value }, setExtraRecords);
onInputChange={(event, val) => {
setInputText("" + val);
debouncedQueryCallback(query, { input: "" + val }, setExtraRecords);
}}
getOptionSelected={(option, value) => (option && option.toString()) === (value && value.toString())}
value={value ? value.toString() : "" + currentValue}
onChange={(event, newValue) => {
setValue(newValue);
setInputText("" + newValue);
getOptionSelected={(option, val) => (option && option.toString()) === (val && val.toString())}
value={inputText !== undefined ? inputText.toString() : "" + currentValue}
onChange={(event, newVal) => {
let newValue = extraRecords.filter(r => r["_fields"][1] == newVal)[0]["_fields"][0];
if (newValue && newValue["low"]) {
newValue = newValue["low"];
}
setValue(newValue);
setInputText("" + newVal);
if (newValue == null && clearParameterOnFieldClear) {
props.setGlobalParameter(parameter, undefined);
props.setGlobalParameter(parameterDisplay, undefined);
} else if (newValue == null) {
props.setGlobalParameter(parameter, defaultValue);
props.setGlobalParameter(parameterDisplay, defaultValue);
} else {
props.setGlobalParameter(parameter, newValue);
props.setGlobalParameter(parameterDisplay, newVal);
}
}}
renderInput={(params) => <TextField {...params} InputLabelProps={{ shrink: true }}
placeholder="Start typing..."
label={helperText ? helperText : label + " " + property} variant="outlined" />}
label={helperText ? helperText : label + " " + propertyDisplay} variant="outlined" />}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too

/>
}
</div>
Expand Down