• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mantidproject / mslice / 21272670920

22 Jan 2026 06:03PM UTC coverage: 86.657% (-0.2%) from 86.88%
21272670920

push

github

web-flow
add a delayed delete to ads workspace removal (#1149)

* add a delated delete

* added changes from Duc's PR

* added null check and renamed methods

* bug fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>

15 of 26 new or added lines in 5 files covered. (57.69%)

2 existing lines in 1 file now uncovered.

3111 of 3590 relevant lines covered (86.66%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.55
/src/mslice/presenters/workspace_manager_presenter.py
1
from .busy import show_busy
1✔
2
from mslice.widgets.workspacemanager.command import Command
1✔
3
from mslice.widgets.workspacemanager import TAB_2D, TAB_NONPSD
1✔
4
from mslice.models.mslice_ads_observer import MSliceADSObserver
1✔
5
from mslice.models.workspacemanager.file_io import get_save_directory
1✔
6
from mslice.models.workspacemanager.workspace_algorithms import (
1✔
7
    save_workspaces,
8
    export_workspace_to_ads,
9
    subtract,
10
    is_pixel_workspace,
11
    combine_workspace,
12
    add_workspace_runs,
13
    scale_workspaces,
14
    remove_workspace_from_ads,
15
)
16
from mslice.models.workspacemanager.workspace_provider import (
1✔
17
    get_workspace_handle,
18
    get_visible_workspace_names,
19
    get_workspace_names,
20
    get_workspace_name,
21
    delete_workspace,
22
    rename_workspace,
23
)
24
from .interfaces.workspace_manager_presenter import WorkspaceManagerPresenterInterface
1✔
25
from .interfaces.main_presenter import MainPresenterInterface
1✔
26
from .validation_decorators import require_main_presenter
1✔
27
from mslice.plotting.globalfiguremanager import GlobalFigureManager
1✔
28

29

30
class WorkspaceManagerPresenter(WorkspaceManagerPresenterInterface):
1✔
31
    def __init__(self, workspace_view):
1✔
32
        # TODO add validation checks
33
        self._workspace_manager_view = workspace_view
1✔
34
        self._main_presenter = None
1✔
35
        self._psd = True
1✔
36
        self._command_map = {
1✔
37
            Command.SaveSelectedWorkspaceNexus: lambda: self._save_selected_workspace(
38
                ".nxs"
39
            ),
40
            Command.SaveSelectedWorkspaceNXSPE: lambda: self._save_selected_workspace(
41
                ".nxspe"
42
            ),
43
            Command.SaveSelectedWorkspaceAscii: lambda: self._save_selected_workspace(
44
                ".txt"
45
            ),
46
            Command.SaveSelectedWorkspaceMatlab: lambda: self._save_selected_workspace(
47
                ".mat"
48
            ),
49
            Command.RemoveSelectedWorkspaces: self._remove_selected_workspaces,
50
            Command.RenameWorkspace: self._rename_workspace,
51
            Command.CombineWorkspace: self._combine_workspace,
52
            Command.SelectionChanged: self._broadcast_selected_workspaces,
53
            Command.Add: self._add_workspaces,
54
            Command.Subtract: self._subtract_workspace,
55
            Command.SaveToADS: self._save_to_ads,
56
            Command.ComposeWorkspace: lambda: self._workspace_manager_view._display_error(
57
                "Please select a Compose action from the dropdown menu"
58
            ),
59
            Command.Scale: self._scale_workspace,
60
            Command.Bose: lambda: self._scale_workspace(is_bose=True),
61
        }
62
        self._ads_observer = MSliceADSObserver(
1✔
63
            self.delete_handle, self.clear_handle, self.rename_handle
64
        )
65
        self._workspaces_to_removed_from_ads = set()
1✔
66

67
    def register_master(self, main_presenter):
1✔
68
        assert isinstance(main_presenter, MainPresenterInterface)
1✔
69
        self._main_presenter = main_presenter
1✔
70
        self._main_presenter.register_workspace_selector(self)
1✔
71
        self.update_displayed_workspaces()
1✔
72

73
    def notify(self, command):
1✔
74
        self._clear_displayed_error()
1✔
75
        with show_busy(self._workspace_manager_view):
1✔
76
            if command in self._command_map.keys():
1✔
77
                self._command_map[command]()
1✔
78
            else:
79
                raise ValueError(
1✔
80
                    "Workspace Manager Presenter received an unrecognised command: {}".format(
81
                        str(command)
82
                    )
83
                )
84

85
    def _broadcast_selected_workspaces(self):
1✔
86
        self.workspace_selection_changed()
1✔
87
        self._get_main_presenter().notify_workspace_selection_changed()
1✔
88

89
    @require_main_presenter
1✔
90
    def _get_main_presenter(self):
1✔
91
        return self._main_presenter
1✔
92

93
    def change_tab(self, tab):
1✔
94
        self._workspace_manager_view.change_tab(tab)
×
95

96
    def highlight_tab(self, tab):
1✔
97
        self._workspace_manager_view.highlight_tab(tab)
×
98

99
    def workspace_selection_changed(self):
1✔
100
        if self._workspace_manager_view.current_tab() == TAB_2D:
1✔
101
            psd = all(
1✔
102
                [
103
                    get_workspace_handle(ws).is_PSD
104
                    for ws in self._workspace_manager_view.get_workspace_selected()
105
                ]
106
            )
107
            if psd and not self._psd:
1✔
108
                self._workspace_manager_view.tab_changed.emit(TAB_2D)
1✔
109
                self._psd = True
1✔
110
            elif not psd and self._psd:
1✔
111
                self._workspace_manager_view.tab_changed.emit(TAB_NONPSD)
1✔
112
                self._psd = False
1✔
113
        else:
114
            # Default is PSD mode, if changed to a non-2D-workspace the GUI resets to the PSD ("Powder") tab
115
            self._psd = True
1✔
116

117
    def _save_selected_workspace(self, extension=None):
1✔
118
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
119
        if not selected_workspaces:
1✔
120
            self._workspace_manager_view.error_select_one_workspace()
1✔
121
            return
1✔
122

123
        try:
1✔
124
            save_directory, save_name, extension = get_save_directory(
1✔
125
                multiple_files=len(selected_workspaces) > 1,
126
                save_as_image=False,
127
                default_ext=extension,
128
            )
129
        except RuntimeError as e:
1✔
130
            if str(e) == "dialog cancelled":
1✔
131
                return
1✔
132
            else:
133
                raise RuntimeError(e)
1✔
134

135
        if not save_directory:
1✔
136
            self._workspace_manager_view.error_invalid_save_path()
1✔
137
            return
1✔
138
        try:
1✔
139
            save_workspaces(selected_workspaces, save_directory, save_name, extension)
1✔
140
        except RuntimeError as e:
1✔
141
            self._workspace_manager_view._display_error(str(e))
1✔
142

143
    def _save_to_ads(self):
1✔
144
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
145
        if not selected_workspaces:
1✔
146
            self._workspace_manager_view.error_select_one_or_more_workspaces()
1✔
147
            return
1✔
148
        for workspace in selected_workspaces:
1✔
149
            export_workspace_to_ads(workspace)
1✔
150

151
    def _remove_selected_workspaces(self):
1✔
152
        if len(self._workspaces_to_removed_from_ads) > 0:
1✔
NEW
153
            self.remove_pending_remove_workspaces_from_ads()
×
154
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
155
        if not selected_workspaces:
1✔
156
            self._workspace_manager_view.error_select_one_or_more_workspaces()
1✔
157
            return
1✔
158
        plotted_windows = GlobalFigureManager.get_plotted_windows_dict()
1✔
159
        for workspace in selected_workspaces:
1✔
160
            ws = get_workspace_handle(workspace)
1✔
161
            if workspace in plotted_windows:
1✔
162
                self._workspaces_to_removed_from_ads.add(ws.name)
1✔
163
            else:
164
                remove_workspace_from_ads(ws.name)
1✔
165
            delete_workspace(workspace)
1✔
166
            self.update_displayed_workspaces()
1✔
167
            if workspace in plotted_windows:
1✔
168
                for plt_window in plotted_windows[workspace]:
1✔
169
                    plt_window.close()
1✔
170

171
    def _rename_workspace(self):
1✔
172
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
173
        if not selected_workspaces:
1✔
174
            self._workspace_manager_view.error_select_one_workspace()
1✔
175
            return
1✔
176
        if len(selected_workspaces) > 1:
1✔
177
            self._workspace_manager_view.error_select_only_one_workspace()
1✔
178
            return
1✔
179
        selected_workspace = selected_workspaces[0]
1✔
180
        new_name = self._workspace_manager_view.get_workspace_new_name()
1✔
181
        if new_name is None:
1✔
182
            return
×
183
        rename_workspace(selected_workspace, new_name)
1✔
184
        self.update_displayed_workspaces()
1✔
185

186
    def _combine_workspace(self):
1✔
187
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
188
        if not selected_workspaces:
1✔
189
            self._workspace_manager_view.error_select_more_than_one_workspaces()
1✔
190
            return
1✔
191
        elif len(selected_workspaces) == 1:
1✔
192
            selected_workspaces.append(
1✔
193
                str(self._workspace_manager_view.add_workspace_dialog())
194
            )
195
        new_workspace = selected_workspaces[0] + "_combined"
1✔
196
        if all([is_pixel_workspace(workspace) for workspace in selected_workspaces]):
1✔
197
            combine_workspace(selected_workspaces, new_workspace)
1✔
198
        else:
199
            self._workspace_manager_view.error_select_more_than_one_workspaces()
1✔
200
            return
1✔
201
        self.update_displayed_workspaces()
1✔
202
        return
1✔
203

204
    def _add_workspaces(self):
1✔
205
        selected_ws = self._workspace_manager_view.get_workspace_selected()
1✔
206
        if not selected_ws:
1✔
207
            self._workspace_manager_view.error_select_one_or_more_workspaces()
1✔
208
            return
1✔
209
        if len(selected_ws) == 1:
1✔
210
            new_ws = self._workspace_manager_view.add_workspace_dialog()
1✔
211
            if new_ws is None:
1✔
212
                return
×
213
            selected_ws.append(new_ws)
1✔
214
        try:
1✔
215
            add_workspace_runs(selected_ws)
1✔
216
        except ValueError as e:
1✔
217
            self._workspace_manager_view._display_error(str(e))
1✔
218
        self.update_displayed_workspaces()
1✔
219

220
    def _subtract_workspace(self):
1✔
221
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
222
        if not selected_workspaces:
1✔
223
            self._workspace_manager_view.error_select_one_or_more_workspaces()
1✔
224
            return
1✔
225
        try:
1✔
226
            background_ws, ssf = self._workspace_manager_view.subtraction_input()
1✔
227
        except RuntimeError:
1✔
228
            return
1✔
229
        try:
1✔
230
            subtract(selected_workspaces, background_ws, ssf)
1✔
231
        except ValueError as e:
1✔
232
            self._workspace_manager_view._display_error(str(e))
1✔
233
        self.update_displayed_workspaces()
1✔
234

235
    def _scale_workspace(self, is_bose=False):
1✔
236
        selected_workspaces = self._workspace_manager_view.get_workspace_selected()
1✔
237
        if not selected_workspaces:
1✔
238
            self._workspace_manager_view.error_select_one_or_more_workspaces()
1✔
239
            return
1✔
240
        try:
1✔
241
            retvals = self._workspace_manager_view.scale_input(is_bose=is_bose)
1✔
242
        except RuntimeError:
1✔
243
            return
1✔
244
        try:
1✔
245
            if is_bose:
1✔
246
                scale_workspaces(
1✔
247
                    selected_workspaces, from_temp=retvals[0], to_temp=retvals[1]
248
                )
249
            else:
250
                scale_workspaces(selected_workspaces, scale_factor=retvals[0])
1✔
251
        except ValueError as e:
1✔
252
            self._workspace_manager_view._display_error(str(e))
1✔
253
        self.update_displayed_workspaces()
1✔
254

255
    def get_selected_workspaces(self):
1✔
256
        """Get the currently selected workspaces from the user"""
257
        return self._workspace_manager_view.get_workspace_selected()
×
258

259
    def set_selected_workspaces(self, workspace_list):
1✔
260
        get_index = self._workspace_manager_view.get_workspace_index
1✔
261
        index_list = []
1✔
262
        for item in workspace_list:
1✔
263
            if isinstance(item, str):
1✔
264
                index_list.append(get_index(item))
1✔
265
            elif isinstance(item, int):
1✔
266
                index_list.append(item)
1✔
267
            else:
268
                index_list.append(get_index(get_workspace_name(item)))
1✔
269
        self._workspace_manager_view.set_workspace_selected(index_list)
1✔
270

271
    def update_displayed_workspaces(self):
1✔
272
        """Update the workspaces shown to user.
273

274
        This function must be called by the main presenter if any other
275
        presenter does any operation that changes the name or type of any existing workspace or creates or removes a
276
        workspace"""
277
        self._workspace_manager_view.display_loaded_workspaces(
1✔
278
            get_visible_workspace_names()
279
        )
280

281
    def _clear_displayed_error(self):
1✔
282
        self._workspace_manager_view.clear_displayed_error()
1✔
283

284
    def delete_handle(self, workspace):
1✔
285
        if workspace.startswith("__MSL"):
×
286
            workspace = workspace[5:]
×
287
        delete_workspace(workspace)
×
288
        self.update_displayed_workspaces()
×
289

290
    def clear_handle(self):
1✔
291
        for workspace in get_workspace_names():
×
292
            delete_workspace(workspace)
×
293
        self.update_displayed_workspaces()
×
294

295
    def rename_handle(self, workspace, new_name):
1✔
296
        if new_name is None:
×
297
            return
×
298
        if workspace in get_visible_workspace_names():
×
299
            rename_workspace(workspace, new_name)
×
300
            self.update_displayed_workspaces()
×
301

302
    def remove_pending_remove_workspaces_from_ads(self):
1✔
NEW
303
        for ws in self._workspaces_to_removed_from_ads:
×
NEW
304
            remove_workspace_from_ads(ws)
×
NEW
305
        self._workspaces_to_removed_from_ads.clear()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc