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

Generate Comparison Charts #3500

Merged
merged 3 commits into from Jul 26, 2023

Conversation

MVarshini
Copy link
Contributor

PBENCH-1208

Generate Comparison Charts

Generate Comparison Charts

zoom feature

modal data change
Copy link
Member

@dbutenhof dbutenhof left a comment

Choose a reason for hiding this comment

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

This mostly looks OK, but I think the handling of the search box (e.g., implicitly accepting regex but quietly quoting all the special characters on a compilation failure) needs some clarification. I've got a few other minor comments, too, including a debugging console.log which should probably be removed. 😁

Comment on lines +40 to +49
if (
error?.response?.data &&
error.response.data?.message
?.toLowerCase()
.includes("unsupported benchmark");
if (isUnsupportedType) {
dispatch({
type: TYPES.IS_UNSUPPORTED_TYPE,
payload: errorMsg,
});
}
.includes("unsupported benchmark")
) {
dispatch({
type: TYPES.IS_UNSUPPORTED_TYPE,
payload: error.response.data.message,
});
Copy link
Member

Choose a reason for hiding this comment

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

So if we get back a server error that's not "unsupported benchmark", we fall through to the else and get a generic error message string? Wouldn't it be better to show the error if there is one, and save the fallback for a communication error that doesn't yield an error?.response?.data?.message?

}, Object.create(null));

for (const [key, value] of Object.entries(result)) {
console.log(key);
Copy link
Member

Choose a reason for hiding this comment

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

Is this a debugging statement you want to keep? 😆


const obj = { options, data };
chartData.push(obj);
i = 0;
Copy link
Member

Choose a reason for hiding this comment

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

Is this "reset" really necessary? Isn't i local to the function?

return map[label];
});
const obj = { label: key, backgroundColor: COLORS[i], data: mappedData };
i++;
Copy link
Member

Choose a reason for hiding this comment

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

You've got 5 COLORS values defined. Is it possible we could have more than 5 datasets selected for comparison? If so, we'll go to COLORS[5] and Javascript won't be happy with you. (If there's other code limiting the selection to 5, that's OK; but at least a comment would be good to clarify that.)

uriTemplate(endpoints, "datasets_compare", {}),
{ params }
);
if (response.status === 200 && response.data.json_data) {
Copy link
Member

Choose a reason for hiding this comment

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

This is fine, I guess, since we really only expect 200 on success; but I still like the idea of using response.ok in general to test "success" except where we care about "alternate success" values. (And the only place the server does that deliberately is on UPLOAD/RELAY where 201/CREATED is distinct from 200/OK (duplicate).)

input = new RegExp(searchValue, "i");
} catch (err) {
input = new RegExp(
searchValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
Copy link
Member

Choose a reason for hiding this comment

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

So you're allowing RegEx characters in the user's filter string, but if it doesn't compile you're quoting all special characters and compiling again? I'm not sure this is what I'd want.

  • If I use regex characters accidentally, quoting them is good, but probably not as a fallback. That is, if I didn't intentionally construct a regex, then the results of AB*C will surprise me;
  • If I construct a regex but get it wrong, quoting all special characters will probably still surprise me.

I think that if we're not going to allow regex input, then you should quote (or reject) special characters up front (or simply use a non-regex filter), and if we intentionally allow regex then a compile failure should be an error visible to the user so they can retry.

I also noticed that in the demos you were using this to find the uperf benchmark runs. As mentioned earlier, using the name is unreliable here: it'd be better to search for an exact match in server.benchmark... although I can imagine users finding name search useful as well, I suspect that in general they'll need a lot more.

I don't think we want to get into this for this PR, but something to keep in mind is that I think perhaps in the longer run what we want is a general search component for datasets that you can use on each page that allows filtering by wildcard (regex?) name match or by arbitrary metadata. That is, when I get to the comparison I probably don't want to see all my/public uperf but rather the set of uperf data I generated for a specific purpose, which might be based on my config string or a global.lightspeed.run value or something.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think users (myself included) will expect a search box to automatically accept a regex unless it is labeled as doing so. Moreover, lots of people are unfamiliar with regex's, and they are going to be surprised if their search string looks like myresult_[2023-7-9].txt and it matches "myresult_80txt".

Perhaps you could have a checkbox or switch which enables regex search? With that "off", there will be no surprises, and with it "on" you can report compilation errors when they occur, which will also prevent surprises.

Copy link
Member

Choose a reason for hiding this comment

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

Also, why are you (implicitly) making the regex case-blind? That seems likely to incur additional surprises.

Copy link
Member

@webbnh webbnh left a comment

Choose a reason for hiding this comment

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

I didn't find anything worth blocking the merge for, but I've pointed out a few code quality issues that you might want to consider addressing.

Comment on lines +40 to +45
if (
error?.response?.data &&
error.response.data?.message
?.toLowerCase()
.includes("unsupported benchmark");
if (isUnsupportedType) {
dispatch({
type: TYPES.IS_UNSUPPORTED_TYPE,
payload: errorMsg,
});
}
.includes("unsupported benchmark")
) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think there is a need to check error?.response?.data before checking error.response.data?.message?.toLowerCase().includes(...) -- this would be a great place to use the ?. operator throughout:

    if (
      error?.response?.data?.message?.toLowerCase().includes("unsupported benchmark")
    ) {

That is, we'll get a false if any of the values are false/undefined/missing, which is what I think you want.

(That said, I agree with Dave's comment below that it would be better if we got the Server's error message instead of a generic, canned one, so we might want to defer or omit the down-casing and the includes(), but I think you can still do it with one test.)

Ditto for the code at line 187.

Comment on lines +142 to +147
export const toggleCompareSwitch = () => (dispatch, getState) => {
dispatch({
type: TYPES.TOGGLE_COMPARE_SWITCH,
payload: !getState().comparison.isCompareSwitchChecked,
});
};
Copy link
Member

Choose a reason for hiding this comment

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

toggleCompareSwitch doesn't need to return a thunk, it could just be the thunk, which would save a function call.

Actually, instead of returning a thunk, toggleCompareSwitch could return just the action, and the action doesn't need to contain a payload field because the reducer already has access to the old isCompareSwitchChecked value and it could calculate the new value from that.

Comment on lines +150 to +155
let selectedIds = [...getState().comparison.selectedResourceIds];
if (isChecked) {
selectedIds = [...selectedIds, rId];
} else {
selectedIds = selectedIds.filter((item) => item !== rId);
}
Copy link
Member

Choose a reason for hiding this comment

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

Alternately, using just const's and a ternary 😉:

  const prev = getState().comparison.selectedResourceIds;
  const selectedIds = isChecked ? [...prev, rId] : prev.filter((id) => id !== rId);

Comment on lines +149 to +160
export const setSelectedId = (isChecked, rId) => (dispatch, getState) => {
let selectedIds = [...getState().comparison.selectedResourceIds];
if (isChecked) {
selectedIds = [...selectedIds, rId];
} else {
selectedIds = selectedIds.filter((item) => item !== rId);
}
dispatch({
type: TYPES.SET_SELECTED_RESOURCE_ID,
payload: selectedIds,
});
};
Copy link
Member

Choose a reason for hiding this comment

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

Why is setSelectedId() a function which returns a thunk? What I think we actually want is a function which returns an action.

I think the reason you're using a thunk here is to get access to getState(). However, I think that's globally available, via useStore().getState(), or you can access the state via useSelector() (or, you can have the caller pass in the state).

So, instead of having the caller dispatch to a thunk which dispatches the action, this function can generate the action and the caller can dispatch it without needing the thunk.

The same comment applies to setChartModalContent().

Comment on lines +161 to +162

export const compareMultipleDatasets = () => async (dispatch, getState) => {
Copy link
Member

Choose a reason for hiding this comment

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

Again, why is this function returning a thunk instead of just being the thunk, which saves a function call.

Comment on lines +65 to +66
width={750}
height={450}
Copy link
Member

Choose a reason for hiding this comment

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

Are these absolute (e.g., pixel) values? Don't we want the size relative to the width of the chart/window/screen?

input = new RegExp(searchValue, "i");
} catch (err) {
input = new RegExp(
searchValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
Copy link
Member

Choose a reason for hiding this comment

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

I don't think users (myself included) will expect a search box to automatically accept a regex unless it is labeled as doing so. Moreover, lots of people are unfamiliar with regex's, and they are going to be surprised if their search string looks like myresult_[2023-7-9].txt and it matches "myresult_80txt".

Perhaps you could have a checkbox or switch which enables regex search? With that "off", there will be no surprises, and with it "on" you can report compilation errors when they occur, which will also prevent surprises.

input = new RegExp(searchValue, "i");
} catch (err) {
input = new RegExp(
searchValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
Copy link
Member

Choose a reason for hiding this comment

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

Also, why are you (implicitly) making the regex case-blind? That seems likely to incur additional surprises.

Comment on lines +30 to +31
}
return item.name.search(input) >= 0;
Copy link
Member

Choose a reason for hiding this comment

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

The docs suggest that, since you want a boolean result, you should use RegExp.test() instead of search():

      return input.test(item.name)

Comment on lines +46 to +47
{filteredDatasets.map((item) => {
return (
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need a code blob here, since all it does is return an expression -- you can just have the arrow function evaluate to the expression (analogous to what you've done at lines 42 and 44).

Copy link
Member

@dbutenhof dbutenhof left a comment

Choose a reason for hiding this comment

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

I would prefer the compare pane search be cleaned up, but let's move this forward so we can stage it soon.

@dbutenhof dbutenhof merged commit e119384 into distributed-system-analysis:main Jul 26, 2023
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants