Commit 2e94ef83 authored by Duggan, John's avatar Duggan, John
Browse files

Allow users to provide their own full directory paths instead of providing a user ID

parent f490b7ff
Loading
Loading
Loading
Loading
Loading
+47 −36
Original line number Diff line number Diff line
@@ -58,14 +58,15 @@ INSTRUMENTS = {
class DataSelectorState(BaseModel, validate_assignment=True):
    """Selection state for identifying datafiles."""

    allow_custom_directories: bool = Field(default=False)
    custom_directories_name: str = Field(default="Custom Directory")
    facility: str = Field(default="", title="Facility")
    instrument: str = Field(default="", title="Instrument")
    experiment: str = Field(default="", title="Experiment")
    user_directory: str = Field(default="", title="User Directory")
    custom_directory: str = Field(default="", title="Custom Directory")
    directory: str = Field(default="")
    extensions: List[str] = Field(default=[])
    prefix: str = Field(default="")
    show_user_directories: bool = Field(default=False)

    @field_validator("experiment", mode="after")
    @classmethod
@@ -81,7 +82,11 @@ class DataSelectorState(BaseModel, validate_assignment=True):
            warn(f"Facility '{self.facility}' could not be found. Valid options: {valid_facilities}", stacklevel=1)

        valid_instruments = self.get_instruments()
        if self.instrument and self.instrument not in valid_instruments:
        if (
            self.instrument
            and self.facility != self.custom_directories_name
            and self.instrument not in valid_instruments
        ):
            warn(
                (
                    f"Instrument '{self.instrument}' could not be found in '{self.facility}'. "
@@ -95,8 +100,8 @@ class DataSelectorState(BaseModel, validate_assignment=True):

    def get_facilities(self) -> List[str]:
        facilities = list(INSTRUMENTS.keys())
        if self.show_user_directories:
            facilities.append("User Directory")
        if self.allow_custom_directories:
            facilities.append(self.custom_directories_name)
        return facilities

    def get_instruments(self) -> List[str]:
@@ -107,14 +112,14 @@ class DataSelectorModel:
    """Manages file system interactions for the DataSelector widget."""

    def __init__(
        self, facility: str, instrument: str, extensions: List[str], prefix: str, show_user_directories: bool
        self, facility: str, instrument: str, extensions: List[str], prefix: str, allow_custom_directories: bool
    ) -> None:
        self.state = DataSelectorState()
        self.state.facility = facility
        self.state.instrument = instrument
        self.state.extensions = extensions
        self.state.prefix = prefix
        self.state.show_user_directories = show_user_directories
        self.state.allow_custom_directories = allow_custom_directories

    def get_facilities(self) -> List[str]:
        return natsorted(self.state.get_facilities())
@@ -155,17 +160,17 @@ class DataSelectorModel:

        return Path("/") / self.state.facility / self.get_instrument_dir() / self.state.experiment

    def get_user_directory_path(self) -> Optional[Path]:
        if not self.state.user_directory:
    def get_custom_directory_path(self) -> Optional[Path]:
        # Don't expose the full file system
        if not self.state.custom_directory:
            return None

        # We don't want to pull all user directory content as there can be an extremely large amount of content.
        # To deal with this, we make an assumption that anything the user wants exposed is placed in the nova directory.
        return Path("/SNS/users") / self.state.user_directory / "nova"
        return Path(self.state.custom_directory)

    def get_directories(self) -> List[str]:
        if self.state.facility == "User Directory":
            base_path = self.get_user_directory_path()
        using_custom_directory = self.state.facility == self.state.custom_directories_name
        if using_custom_directory:
            base_path = self.get_custom_directory_path()
        else:
            base_path = self.get_experiment_directory_path()

@@ -174,6 +179,12 @@ class DataSelectorModel:

        directories = []
        try:
            if using_custom_directory:
                for entry in os.listdir(base_path):
                    path = base_path / entry
                    if os.path.isdir(path):
                        directories.append({"path": str(path), "title": entry})
            else:
                for dirpath, _, _ in os.walk(base_path):
                    # Get the relative path from the start path
                    path_parts = os.path.relpath(dirpath, base_path).split(os.sep)
+12 −11
Original line number Diff line number Diff line
@@ -23,12 +23,12 @@ class DataSelector(datagrid.VGrid):
    def __init__(
        self,
        v_model: str,
        allow_custom_directories: bool = False,
        facility: str = "",
        instrument: str = "",
        extensions: Optional[List[str]] = None,
        prefix: str = "",
        select_strategy: str = "all",
        show_user_directories: bool = False,
        **kwargs: Any,
    ) -> None:
        """Constructor for DataSelector.
@@ -38,6 +38,9 @@ class DataSelector(datagrid.VGrid):
        v_model : str
            The name of the state variable to bind to this widget. The state variable will contain a list of the files
            selected by the user.
        allow_custom_directories : bool, optional
            Whether or not to allow users to provide their own directories to search for datafiles in. Ignored if the
            facility parameter is set.
        facility : str, optional
            The facility to restrict data selection to. Options: HFIR, SNS
        instrument : str, optional
@@ -50,9 +53,6 @@ class DataSelector(datagrid.VGrid):
        select_strategy : str, optional
            The selection strategy to pass to the `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`__.
            If unset, the `all` strategy will be used.
        show_user_directories : bool, optional
            Whether or not to allow users to select data files from user directories. Ignored if the facility parameter
            is set. Please note that the component only looks for a "nova" directory and ignores all other content.
        **kwargs
            All other arguments will be passed to the underlying
            `VDataTable component <https://trame.readthedocs.io/en/latest/trame.widgets.vuetify3.html#trame.widgets.vuetify3.VDataTable>`_.
@@ -69,15 +69,16 @@ class DataSelector(datagrid.VGrid):
        else:
            self._label = None

        if facility and show_user_directories:
            warn("show_user_directories will be ignored since the facility parameter is set.", stacklevel=1)
        if facility and allow_custom_directories:
            warn("allow_custom_directories will be ignored since the facility parameter is set.", stacklevel=1)

        self._v_model = v_model
        self._v_model_name_in_state = v_model.split(".")[0]
        self._allow_custom_directories = allow_custom_directories
        self._custom_directories_name = "Custom Directory"
        self._extensions = extensions if extensions is not None else []
        self._prefix = prefix
        self._select_strategy = select_strategy
        self._show_user_directories = show_user_directories

        self._revogrid_id = f"nova__dataselector_{self._next_id}_rv"
        self._state_name = f"nova__dataselector_{self._next_id}_state"
@@ -110,19 +111,19 @@ class DataSelector(datagrid.VGrid):
                if instrument == "":
                    columns -= 1
                    InputField(
                        v_if=f"{self._state_name}.facility !== 'User Directory'",
                        v_if=f"{self._state_name}.facility !== '{self._custom_directories_name}'",
                        v_model=f"{self._state_name}.instrument",
                        items=(self._instruments_name,),
                        type="autocomplete",
                    )
                InputField(
                    v_if=f"{self._state_name}.facility !== 'User Directory'",
                    v_if=f"{self._state_name}.facility !== '{self._custom_directories_name}'",
                    v_model=f"{self._state_name}.experiment",
                    column_span=columns,
                    items=(self._experiments_name,),
                    type="autocomplete",
                )
                InputField(v_else=True, v_model=f"{self._state_name}.user_directory", column_span=2)
                InputField(v_else=True, v_model=f"{self._state_name}.custom_directory", column_span=2)

            with GridLayout(columns=2, classes="flex-1-0 h-0", valign="start"):
                if not self._prefix:
@@ -198,7 +199,7 @@ class DataSelector(datagrid.VGrid):

    def create_model(self, facility: str, instrument: str) -> None:
        self._model = DataSelectorModel(
            facility, instrument, self._extensions, self._prefix, self._show_user_directories
            facility, instrument, self._extensions, self._prefix, self._allow_custom_directories
        )

    def create_viewmodel(self) -> None:
+0 −1
Original line number Diff line number Diff line
@@ -85,7 +85,6 @@ class RevoGrid {
            },
        })

        console.log(inputVNode)
        return [inputVNode, 'Available Datafiles']
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ class DataSelectorViewModel:
                    self.reset()
                case "experiment":
                    self.reset()
                case "user_directory":
                case "custom_directory":
                    self.reset()
        self.update_view()

+2 −2
Original line number Diff line number Diff line
@@ -340,8 +340,8 @@ class App(ThemedApp):
                            vuetify.VTab("Tab 3")

                    vuetify.VCardTitle("Data Selection Widgets")
                    with html.Div(classes="border-md text-left", style="height: 650px;"):
                        DataSelector(v_model="data_selector.selected_files", chips=True)
                    with html.Div(classes="border-md text-left", style="height: 650px; width: 400px;"):
                        DataSelector(v_model="data_selector.selected_files", allow_custom_directories=True, chips=True)

                    vuetify.VCardTitle("Form Inputs & Controls")
                    with GridLayout(columns=3, valign="center"):