-
-
Notifications
You must be signed in to change notification settings - Fork 1k
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
[ENH] Allow custom (generic) names in Transpose Widget #2737
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2737 +/- ##
==========================================
+ Coverage 76.1% 76.14% +0.03%
==========================================
Files 338 338
Lines 59777 59839 +62
==========================================
+ Hits 45496 45566 +70
+ Misses 14281 14273 -8 |
Not like this. Keep two radio buttons, Generic and From meta attribute. Generic than has a line edit with placeholder Feature. Indent the line edit so that it is aligned with the combo. |
Orange/widgets/data/owtranspose.py
Outdated
@@ -7,6 +11,28 @@ | |||
from Orange.widgets.widget import Input, Output | |||
|
|||
|
|||
def add_radio_buttons(widget, master, value, btn_labels=(), orientation=Qt.Vertical, callback=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this function at all. It is called add_radio_buttons
, and it appends radio buttons and line edits. And then a combo is added separately since this function handles only line edits. (Well, if it handled everything, it would just replace any placing of components inside control area.)
It looks general, but is called only once and replace what we would otherwise do with a sequence of calls to gui
method that we usually use for constructing the interface. I would suggest refactoring this code into __init__
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to make it general, well then I moved everything into __init__
.
Orange/widgets/data/owtranspose.py
Outdated
self.feature_type = self.GENERIC | ||
self.custom_feature_name.setText(self.custom_feature_name.text().strip()) | ||
self.feature_name = self.custom_feature_name.text() | ||
if self.auto_apply: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why if self.auto_apply
? gui.auto_commit
already replaced the apply method with one that applies only when outputs data if auto_apply
is one.
The condition is not only redundant, it breaks the widget. Disable auto apply. Change the line edit. Enable auto apply. This should update the output but it doesn't since the dirty flag was not set because the apply
function was not called when the auto apply was off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you added if
to stop the changes from being applied when auto apply was disabled: the problem is that the auto apply button is a default button, hence Enter triggers it. To prevent this, use self.apply_button.button.setAutoDefault(False)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly, that was the reason I added that if
.
Orange/widgets/data/owtranspose.py
Outdated
def _apply_editing(self): | ||
self.feature_type = self.GENERIC | ||
self.custom_feature_name.setText(self.custom_feature_name.text().strip()) | ||
self.feature_name = self.custom_feature_name.text() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines get the text from the line edit, strip it, set it back to the line edit, and then read it from the line edit back to the widget's attribute.
self.feature_name = self.feature_name.strip()
would do the whole job if gui.lineedit
was used.
Orange/widgets/data/owtranspose.py
Outdated
bg.group = rb | ||
bg.buttons = [] | ||
bg.ogValue = value = "feature_type" | ||
bg.ogMaster = self |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code comes from gui.radioButtons
! Why copying it instead of using it?
Orange/widgets/data/owtranspose.py
Outdated
bg.ogValue = value = "feature_type" | ||
bg.ogMaster = self | ||
|
||
for i, label in enumerate(["Generic", edit, "From meta attribute:"]): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This loop contains an if. The first case happens twice. It could be replaced with a single line (instead of two here -- the condition and the line). The elif
part happens once. Get rid of the loop and the if's. The code will be two lines shorter and way more readable.
Orange/widgets/data/owtranspose.py
Outdated
box, self, "feature_type", callback=lambda: self.apply(), | ||
btnLabels=["Generic", "From meta attribute:"]) | ||
|
||
self.custom_feature_name = edit = QLineEdit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not gui.lineEdit
?! It would simplify the code; that's why we have gui
.
Orange/widgets/data/owtranspose.py
Outdated
|
||
def _get_feature_name(self): | ||
edit = self.custom_feature_name | ||
return edit.text().strip() or edit.placeholderText() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer having the generic text as a constant instead of copying from the placeholder.
Orange/widgets/data/owtranspose.py
Outdated
|
||
def apply(self): | ||
self.clear_messages() | ||
transposed = None | ||
kwargs = {} if self.feature_type != self.GENERIC else \ | ||
{"feature_name": self._get_feature_name()} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just give the feature_name
argument, even if the method won't use it. Better than this hack.
Orange/widgets/data/owtranspose.py
Outdated
self.feature_type = int(enabled) | ||
self.feature_type = self.FROM_META_ATTR if enabled else self.GENERIC | ||
|
||
def _apply_editing(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this function before _feature_combo_changed
; they belong together since they handle the same type of event.
Orange/widgets/data/owtranspose.py
Outdated
@@ -76,25 +106,40 @@ def update_controls(self): | |||
if self.feature_model: | |||
self.feature_names_column = self.feature_model[0] | |||
enabled = bool(self.feature_model) | |||
self.feature_radio.buttons[1].setEnabled(enabled) | |||
self.feature_radio.buttons[self.FROM_META_ATTR].setEnabled(enabled) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The previous version allowed changing the radio button, but prevented changing the combo when no data. I don't see much point in disabling the combo and lineedit, and leave the radios on.
On the other hand, is there any harm in letting the user monkey around the controls when there is no data? I'd just leave them enabled.
I pushed my take on it. |
def test_transpose_custom_name(self): | ||
""" | ||
Custom name can be set. | ||
GH-2737 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion -- and I would want to hear what @astaric, @lanzagar and @kernc think about it -- tests should test some functionality and not specific bugs that were discovered, or be related to some specific issue in any way. Hence, I do not like comments like GH-2737. This makes our unit tests look like a bunch of tests for patches. This is irrelevant -- potential bugs must be covered by "normal" tests, but bug-specific tests.
This bothered me for some time already, but now I stumbled upon this again when I wanted to reorganize tests for this widget, and I decided to "dissolve" this test into some other test.
Again, this is just my opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but bug-specific tests. -> not bug specific tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixing bugs or commonplace stuff like test_report, sure. But in general, it's not necessarily obvious why a certain feature was added in amendments as opposed to initially. It's customary to cross reference the issue(s) where/when the surrounding discussion occurred. Case by case; ymmv.
|
||
def test_send_report(self): | ||
""" | ||
GH-2773 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is an even better example: test_send_report
should be a test for reports. Why is it marked as "GH-2737"?
Orange/widgets/data/owtranspose.py
Outdated
edit = gui.lineEdit( | ||
gui.indentedBox(box, gui.checkButtonOffsetHint(button)), self, | ||
"feature_name", | ||
placeholderText="Prefix for column names", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, I can not read the whole placeholder text because the widget is too small and not resizable.
We can leave the text as is and just make the widget a bit larger?
widget.apply() | ||
output = self.get_output(widget.Outputs.data) | ||
self.assertTrue( | ||
all(x.name.startswith("Feature ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to test that names start with OWTranspose.DEFAULT_PREFIX
?
(in case someone changes the default in the future)
widget.apply() | ||
output = self.get_output(widget.Outputs.data) | ||
self.assertTrue( | ||
all(x.name.startswith("Feature ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to test that names start with OWTranspose.DEFAULT_PREFIX
?
(in case someone changes the default in the future)
Issue
Fixes #2729
Description of changes
Includes