-
Notifications
You must be signed in to change notification settings - Fork 9
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: find and replace functionality #198
Conversation
…enums, fine tune row widgets
…fore and after in preview, improve replace behavior
This is basically feature complete as far as I care for. There was a good amount of gui-ing here so I'm hoping for some adversarial clicking while I write comments / unit tests. |
atef/ui/fill_template_page.ui
Outdated
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.
Clickly clicky seemed to work as I used this and it seems very powerful in a way that I think will be well-appreciated by the users. A few thoughts:
- Subbing into a big structure can sometimes show a bit too much info. Is there any way we can truncate this so we only show the bit that changed?
- Is there any simple way we can automatically refresh when I add a new edit? I got confused for a moment at first before realizing that I needed to click the refresh button on the left.
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.
Oh, also: after saving the file, it seems like this whole template window goes blank. I think that will scare some people. I wonder if we should automatically close this and open the file that was just saved?
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.
Subbing into a big structure can sometimes show a bit too much info. Is there any way we can truncate this so we only show the bit that changed?
I gave this my best effort, but I wonder if I can do any better. Currently it shows the deepest dataclass, which I thought was needed to give the change any context at all. But I suppose now that I have {object}.{attr} on the left I could just show the {attr} on the right
Is there any simple way we can automatically refresh when I add a new edit? I got confused for a moment at first before realizing that I needed to click the refresh button on the left.
This is a perpetual issue I have. I always try to hook more editingFinished
signals, but find them to either not catch all the cases I'd want or trigger too often. I'll see what I can do here
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've made the previews show just the bit that changed (the last part of the path), as well as gave the user some notice that their file was saved and that they can open it.
I'll give a bit more information in the details list if the user hasn't picked an edit as well
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.
What would be really neat is if you could preview the checkout bits that changed, maybe even just a color-coded tree of sorts?
Might be neat if the tooltip could show an ASCII tree for the key:
Something to the effect of:
configs[0]
- by_attr...
(Weighing an ascii tooltip tree up against the rest of atef functionality... totally top priority, yeah?)
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 did think about this, but punted it never to look back. I think it's honestly the ideal visualization, and might not be too much more work, the more I think about it...
This is definitely worth a follow up issue though
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'm very positive on this. Pending Ken's review I think this is good enough to merge.
atef/widgets/config/find_replace.py
Outdated
preview_file = copy.deepcopy(self.data.target) | ||
|
||
try: | ||
_ = data.apply(target=preview_file) |
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.
Is there some special reason to hold on to this reference?
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.
Ah I dropped my train of thought here. FindReplaceAction.apply
catches errors now, so I need that value to decide whether or not application was successful
refresh_button = self.button_box.button(QtWidgets.QDialogButtonBox.Retry) | ||
refresh_button.clicked.connect(self.refresh_paths) | ||
refresh_button.setText('') | ||
refresh_button.setToolTip('refresh edit details') | ||
refresh_button.setIcon(qta.icon('ei.refresh')) | ||
|
||
apply_button = self.button_box.button(QtWidgets.QDialogButtonBox.Apply) | ||
apply_button.clicked.connect(self.apply_edits) | ||
apply_button.setText('') | ||
apply_button.setToolTip('apply all changes') | ||
apply_button.setIcon(qta.icon('ei.check')) |
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 lack of self.<thing> = <thing>
here confused me, but then I realized that the button_box must be tracking them.
|
||
self.actions.append(action) | ||
|
||
# this works, but is smelly and bad. Access parent table signals |
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 think the canonical way to do this would be to put a signal on this class, emit it here, and connect it to the parent's parent's signals at some point in the parent's parent, rather than parent twicing. I think this is OK as-is though.
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.
Adding a signal on this row that the table widget expects would force me to change the other instances of this table-row-combo to match (and also have that signal), which I felt wasn't worth it at this juncture. I totally agree though, this isn't quite how things should be done.
@@ -367,13 +374,39 @@ def clear_results(self, *args, **kwargs): | |||
|
|||
current_tree.update_statuses() | |||
|
|||
def find_replace(self, *args, **kwargs): | |||
""" find and replace in the current tree. Open a find-replace widget """ |
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 didn't notice this while testing- makes me want to go back and click around again.
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 is really nifty
I wonder if anyone will get confused by the interface though- it wasn't obvious to me at first that this wasn't doing a find/replace in the same window, rather it is creating a whole new version that I could choose to edit and save seperately
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.
Yeah, it is really cool but I also felt the same regarding the end user's expectations with the dialog.
"Apply All" seems like it should be replacing it in the file, but it's just doing the changes on an in-memory version until you click 'save as' (... right?)
Maybe the dialog should be something like:
"Choose input file: "
"Choose output file: "
(Make edits in this section)
Reload / Preview and Verify / Save
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 functionality here is awesome! Nicely done. I think this will serve the end user rather well.
Some thoughts, suggestions, nitpicks, and musings as per usual. Nothing at a level that requires holding this PR up any longer. Sorry it took me so long to get around to reviewing!
atef/widgets/config/find_replace.py
Outdated
item: Any, | ||
match: Callable, | ||
parent: List[Tuple[Any, Any]] = [] | ||
) -> Generator: |
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.
) -> Generator: | |
) -> Generator[List[Tuple[Any, Any]], None, None]: |
atef/widgets/config/find_replace.py
Outdated
self.update_replace_fn() | ||
|
||
self.change_list.clear() | ||
self.match_paths = walk_find_match(self.orig_file, self._match_fn) |
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.
Careful - you exhaust this below so self.match_paths
isn't useful
It'd be better to listify it here, probably, even if just due to the dataclass change comment below on 454
atef/ui/fill_template_page.ui
Outdated
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.
What would be really neat is if you could preview the checkout bits that changed, maybe even just a color-coded tree of sorts?
Might be neat if the tooltip could show an ASCII tree for the key:
Something to the effect of:
configs[0]
- by_attr...
(Weighing an ascii tooltip tree up against the rest of atef functionality... totally top priority, yeah?)
atef/widgets/config/find_replace.py
Outdated
return | ||
elif not edit_row_widget.get_details_rows(): | ||
l_item = QtWidgets.QListWidgetItem( | ||
'provide search and replace text to show details...' |
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.
refresh_button.setToolTip('refresh edit details') | ||
refresh_button.setIcon(qta.icon('ei.refresh')) | ||
|
||
apply_button = self.button_box.button(QtWidgets.QDialogButtonBox.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.
data = deserialize(ProcedureFile, self._original_json) | ||
except ValidationError: | ||
logger.error('failed to open file as either active ' | ||
'or passive checkout') |
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 should probably raise
and clear original_json
At the very least data
is unbound in this except clause
atef/widgets/config/find_replace.py
Outdated
self, | ||
*args, | ||
filepath: Optional[str] = None, | ||
window: Optional[Any] = 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.
Probably can do better than Any
here
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.
TIL typing.TYPE_CHECKING
was a thing. circular imports be gone!
def __init__( | ||
self, | ||
*args, | ||
data=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.
data
unused?
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.
My TableWidgetWithAddRow
expected its row widgets to be DataWidget
s, all of which have a data
kwarg. I probably could have made a dataclass to represent an "Edit" but apparently past me didn't feel like it 🤷
""" | ||
Update the title. Will be the name and the number of unapplied edits | ||
""" | ||
file_name = os.path.basename(self.fp) |
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.
Dealing with None
is annoying with respect to the static analyzer but I find it to be good practice - even if you're 100% sure this doesn't get called when fp is None
- to guard for it anyway. A future refactor you may forget about it and then get confused why basename(None)
is getting called
self.toggle_button = QtWidgets.QToolButton(text=text) | ||
self.toggle_button.setCheckable(True) | ||
self.toggle_button.setChecked(False) | ||
self.toggle_button.setStyleSheet("QToolButton { border: 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.
Aside: we should probably move to making an atef stylesheet at some point and move these things there (atef.qss
, oh yeah!)
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 wonder if that'd fix some of the weird osx rendering differences 🤔 A good idea, but I'm not volunteering right now... 👀
…pare successfully
…hold onto failed components
I think I've addressed all the comments that fit as add-ons to this PR, and made issues for the rest. I'd like to merge this and move on, unless there are any objections |
Merge at will! |
Description
Motivation and Context
This stems from a request for templated checkouts, which boils down to having find-and-replace functionality. A naive find-and-replace on the json could touch file/field names, and is rather difficult to inspect.
My intention is to make the future "fill template" step / tool wrap this functionality, possibly with some extra visualization or streamlining buttons. A sample checkout could be run through this tool and and "filled" as is with this tool, I imagine
(In reality I implemented the simple find-and-replace helpers, tried making it more robust, and a week later ended up here.)
Creating a step that fills templates mid-checkout is a possible next step, though we'd have to consider expanding each template step on preparation and inserting it into the tree. Inspection of the substeps of that filled template would be un-inspectable unless we expand in edit-mode, and in that case we're basically creating a bunch of filled template checkouts separately.
How Has This Been Tested?
Some unit tests have been added, more need to be...
Where Has This Been Documented?
This PR
Screenshots
Pre-merge checklist
docs/pre-release-notes.sh
and created a pre-release documentation page