diff --git a/.github/workflows/init.yml b/.github/workflows/init.yml deleted file mode 100644 index 2aa5cad..0000000 --- a/.github/workflows/init.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Workflow runs only once when the template is first used. -# File can be safely deleted after repo is initialized. -name: Initialize repository -on: - push: - branches: - - main - -jobs: - initialize-package: - name: Initialize the package - if: ${{github.event.repository.name != 'aind-library-template'}} - runs-on: ubuntu-latest - env: - REPO_NAME: ${{ github.event.repository.name }} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Rename package - run: | - pkg_name=$(echo "${REPO_NAME}" | tr - _) - current_description='description = "Prints messages to stdout. Simple boilerplate for libraries."' - new_description='description = "Generated from aind-library-template"' - readme_description='Template for a minimal, basic repository for an AIND library.' - new_readme_description='Generated from aind-library-template' - echo "Package Name ${pkg_name}" - mkdir src/${pkg_name} - touch src/${pkg_name}/__init__.py - echo '"""Init package"""' >> src/${pkg_name}/__init__.py - echo '__version__ = "0.0.0"' >> src/${pkg_name}/__init__.py - sed -i "s/aind_library_template/${pkg_name}/" pyproject.toml - sed -i "s/aind-library-template/${REPO_NAME}/" pyproject.toml - sed -i "s/aind_library_template/${pkg_name}/" docs/source/conf.py - sed -i "s/${current_description}/${new_description}/" pyproject.toml - sed -i "/pandas/d" pyproject.toml - sed -i "s/aind-library-template/${REPO_NAME}/" README.md - sed -i "s/${readme_description}/${new_readme_description}/" README.md - - name: Commit changes - uses: EndBug/add-and-commit@v9 - with: - default_author: github_actions - message: "ci: version bump [skip actions]" - add: '["pyproject.toml", "README.md", "src/*", "docs/source/conf.py"]' - remove: '["-r src/aind_library_template", "tests/test_message_handler.py"]' - - name: Add first tag - run: | - git tag v0.0.0 - git push origin v0.0.0 - - name: Disable workflow - run: | - gh workflow disable -R $GITHUB_REPOSITORY "${{ github.workflow }}" diff --git a/.github/workflows/test_and_lint.yml b/.github/workflows/test_and_lint.yml index 7ce7f31..e70d6dc 100644 --- a/.github/workflows/test_and_lint.yml +++ b/.github/workflows/test_and_lint.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - dev jobs: ci: diff --git a/src/aind_qc_portal/projects/dataset.py b/src/aind_qc_portal/projects/dataset.py index 8f6f03a..0057c5b 100644 --- a/src/aind_qc_portal/projects/dataset.py +++ b/src/aind_qc_portal/projects/dataset.py @@ -3,7 +3,11 @@ import param from aind_qc_portal.docdb.database import get_project_data -from aind_qc_portal.utils import format_link, qc_status_color_css, qc_status_link_html +from aind_qc_portal.utils import ( + format_link, + qc_status_color_css, + qc_status_link_html, +) from aind_data_schema.core.quality_control import QualityControl, Status ALWAYS_COLUMNS = ["Subject ID", "Date"] @@ -35,16 +39,12 @@ def __init__(self, project_name: str, **params): self.project_name = project_name self._df = pd.DataFrame(columns=["_id", "timestamp"]) - self.subject_selector = pn.widgets.MultiChoice( - name="Subject ID" - ) - self.columns_selector = pn.widgets.MultiChoice( - name="Columns" - ) - self.derived_selector = pn.widgets.Select(name="Derived", options=["All", "Raw", "Derived"]) - self.type_selector = pn.widgets.Select( - name="Type" + self.subject_selector = pn.widgets.MultiChoice(name="Subject ID") + self.columns_selector = pn.widgets.MultiChoice(name="Columns") + self.derived_selector = pn.widgets.Select( + name="Derived", options=["All", "Raw", "Derived"] ) + self.type_selector = pn.widgets.Select(name="Type") self.status_selector = pn.widgets.Select( name="QC Status", options=QC_STATUS_OPTIONS ) @@ -52,7 +52,11 @@ def __init__(self, project_name: str, **params): self._get_assets() self.subject_selector.options = self.subjects - self.columns_selector.options = [column for column in self.columns if column not in ALWAYS_COLUMNS + HIDDEN_COLUMNS] + self.columns_selector.options = [ + column + for column in self.columns + if column not in ALWAYS_COLUMNS + HIDDEN_COLUMNS + ] self.type_selector.options = ["All"] + self.types def _get_assets(self): @@ -102,7 +106,9 @@ def _get_assets(self): record_data = { "_id": record.get("_id"), - "Raw Data": record.get("data_description", {}).get("data_level") + "Raw Data": record.get("data_description", {}).get( + "data_level" + ) == "raw", "project_name": record.get("data_description", {}).get( "project_name" @@ -141,11 +147,16 @@ def _get_assets(self): lambda x: ", ".join(x) if x else None ) self._df["QC Status"] = self._df.apply( - lambda row: qc_status_link_html(row["QC Status"], row["qc_link"], row["QC Status"]), axis=1 + lambda row: qc_status_link_html( + row["QC Status"], row["qc_link"], row["QC Status"] + ), + axis=1, ) print(self._df["QC Status"].values[0]) - self._df.drop(columns=["qc_link", "operator", "session_start_time", "location"]) + self._df.drop( + columns=["qc_link", "operator", "session_start_time", "location"] + ) # Sort dataframe by time and then by subject ID self._df.sort_values(by="timestamp", ascending=True, inplace=True) @@ -168,13 +179,12 @@ def _data_filtered(self) -> pd.DataFrame: if self.derived_filter != "All": filtered_df = filtered_df[ - filtered_df["Raw Data"] == (True if self.derived_filter == "Raw" else False) + filtered_df["Raw Data"] + == (True if self.derived_filter == "Raw" else False) ] if self.type_filter != "All": - filtered_df = filtered_df[ - filtered_df["Type"] == self.type_filter - ] + filtered_df = filtered_df[filtered_df["Type"] == self.type_filter] if self.status_filter != "All": filtered_df = filtered_df[ diff --git a/src/aind_qc_portal/projects/project_view.py b/src/aind_qc_portal/projects/project_view.py index 977c678..2b4b4f4 100644 --- a/src/aind_qc_portal/projects/project_view.py +++ b/src/aind_qc_portal/projects/project_view.py @@ -41,12 +41,11 @@ def __init__(self, project_name: str, dataset: ProjectDataset): self.history_chart = self.history_panel() if hasattr(self.history_chart, "selection"): self.selection_history_chart = pn.bind( - self.selection_history_panel, self.history_chart.selection.param.brush + self.selection_history_panel, + self.history_chart.selection.param.brush, ) else: - self.selection_history_chart = pn.widgets.StaticText( - value="" - ) + self.selection_history_chart = pn.widgets.StaticText(value="") @property def has_data(self): @@ -59,8 +58,7 @@ def get_asset_count(self): return len(self.dataset.data_filtered()) def update_subject_selector(self, event): - """Update the subject selector based on the brush selection - """ + """Update the subject selector based on the brush selection""" if event.new.get("Subject ID") is not None: self.dataset.subject_selector.value = event.new["Subject ID"] else: @@ -76,8 +74,10 @@ def history_panel(self): data = self.dataset.data # Check that timestamp column has values - if data['timestamp'].isnull().all(): - return pn.widgets.StaticText(value="Data processing error: project is missing timestamp data in some assets. Please reach out to scientific computing for help repairing your metadata.") + if data["timestamp"].isnull().all(): + return pn.widgets.StaticText( + value="Data processing error: project is missing timestamp data in some assets. Please reach out to scientific computing for help repairing your metadata." + ) # Calculate the time range to show on the x axis (min_range, max_range, range_unit, format) = df_timestamp_range( @@ -102,7 +102,9 @@ def history_panel(self): alt.Tooltip("Date:T", title="Date"), ], color=alt.condition( - self.brush, alt.Color("Subject ID:N"), alt.value("lightgray") + self.brush, + alt.Color("Subject ID:N"), + alt.value("lightgray"), ), href=alt.Href("qc_link:N"), ) @@ -125,7 +127,9 @@ def selection_history_panel(self, selection): data = self.dataset.data_filtered() if data.empty: - return pn.widgets.StaticText(value="No data found for the selected filters") + return pn.widgets.StaticText( + value="No data found for the selected filters" + ) # Calculate the time range to show on the x axis (min_range, max_range, range_unit, format) = df_timestamp_range( @@ -170,12 +174,14 @@ def selection_history_panel(self, selection): return pn.pane.Vega(chart, sizing_mode="stretch_width") - def _panel(self, - subject_filter, - derived_filter, - columns_filter, - type_filter, - status_filter) -> pn.Column: + def _panel( + self, + subject_filter, + derived_filter, + columns_filter, + type_filter, + status_filter, + ) -> pn.Column: """Return panel object""" self.dataset.subject_filter = subject_filter @@ -192,11 +198,15 @@ def _panel(self, def panel(self): - return pn.Column(self.history_chart, pn.bind( - self._panel, - subject_filter=self.dataset.subject_selector, - derived_filter=self.dataset.derived_selector, - columns_filter=self.dataset.columns_selector, - type_filter=self.dataset.type_selector, - status_filter=self.dataset.status_selector, - ), styles=OUTER_STYLE) + return pn.Column( + self.history_chart, + pn.bind( + self._panel, + subject_filter=self.dataset.subject_selector, + derived_filter=self.dataset.derived_selector, + columns_filter=self.dataset.columns_selector, + type_filter=self.dataset.type_selector, + status_filter=self.dataset.status_selector, + ), + styles=OUTER_STYLE, + ) diff --git a/src/aind_qc_portal/qc_project_app.py b/src/aind_qc_portal/qc_project_app.py index ae860da..3e746ae 100644 --- a/src/aind_qc_portal/qc_project_app.py +++ b/src/aind_qc_portal/qc_project_app.py @@ -91,9 +91,7 @@ def panel(self): dataset.subject_selector.value = dataset.subject_filter dataset.derived_selector.value = dataset.derived_filter dataset.columns_selector.value = [ - column - for column in dataset.columns_filter - if column not in ALWAYS_COLUMNS + column for column in dataset.columns_filter if column not in ALWAYS_COLUMNS ] dataset.type_selector.value = dataset.type_filter dataset.status_selector.value = dataset.status_filter diff --git a/tests/test_utils.py b/tests/test_utils.py index 339fb40..478abf2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -12,20 +12,35 @@ df_timestamp_range, replace_markdown_with_html, qc_status_color_css, - bincount2D + bincount2D, ) class TestUtils(unittest.TestCase): def test_format_link(self): - self.assertEqual(format_link("http://example.com"), 'link') - self.assertEqual(format_link("http://example.com", "Example"), 'Example') + self.assertEqual( + format_link("http://example.com"), + 'link', + ) + self.assertEqual( + format_link("http://example.com", "Example"), + 'Example', + ) def test_status_html(self): - self.assertEqual(qc_status_html(Status.PASS), 'Pass') - self.assertEqual(qc_status_html(Status.PENDING), 'Pending') - self.assertEqual(qc_status_html(Status.FAIL), 'Fail') + self.assertEqual( + qc_status_html(Status.PASS), + 'Pass', + ) + self.assertEqual( + qc_status_html(Status.PENDING), + 'Pending', + ) + self.assertEqual( + qc_status_html(Status.FAIL), + 'Fail', + ) def test_df_timestamp_range(self): data = { @@ -41,13 +56,24 @@ def test_df_timestamp_range(self): self.assertEqual(fmt, "%b %d") def test_md_style(self): - self.assertEqual(replace_markdown_with_html(12, "test"), 'test') + self.assertEqual( + replace_markdown_with_html(12, "test"), + 'test', + ) def test_qc_color(self): - self.assertEqual(qc_status_color_css("No QC"), "background-color: #FFB71B") - self.assertEqual(qc_status_color_css("Pass"), "background-color: #1D8649") - self.assertEqual(qc_status_color_css("Fail"), "background-color: #FF5733") - self.assertEqual(qc_status_color_css("Pending"), "background-color: #2A7DE1") + self.assertEqual( + qc_status_color_css("No QC"), "background-color: #FFB71B" + ) + self.assertEqual( + qc_status_color_css("Pass"), "background-color: #1D8649" + ) + self.assertEqual( + qc_status_color_css("Fail"), "background-color: #FF5733" + ) + self.assertEqual( + qc_status_color_css("Pending"), "background-color: #2A7DE1" + ) def test_bincount2D(self): x = np.array([1, 2, 2, 3]) @@ -57,17 +83,25 @@ def test_bincount2D(self): self.assertTrue(np.array_equal(xscale, np.array([1, 2, 3]))) self.assertTrue(np.array_equal(yscale, np.array([4, 5, 6]))) - @patch('aind_qc_portal.utils.pn') + @patch("aind_qc_portal.utils.pn") def test_set_background(self, mock_pn): mock_pn.config.raw_css = [] # Mock raw_css to ensure a clean state - mock_pn.state.location.query_params = {} # Mock query_params to ensure no background param + mock_pn.state.location.query_params = ( + {} + ) # Mock query_params to ensure no background param format_css_background() - self.assertIn("background-color: #003057", mock_pn.config.raw_css[0]) # Default dark_blue color + self.assertIn( + "background-color: #003057", mock_pn.config.raw_css[0] + ) # Default dark_blue color mock_pn.config.raw_css = [] # Reset mock raw_css - mock_pn.state.location.query_params = {"background": "light_blue"} # Mock query_params with light_blue + mock_pn.state.location.query_params = { + "background": "light_blue" + } # Mock query_params with light_blue format_css_background() - self.assertIn("background-color: #2A7DE1", mock_pn.config.raw_css[0]) # light_blue color + self.assertIn( + "background-color: #2A7DE1", mock_pn.config.raw_css[0] + ) # light_blue color if __name__ == "__main__":