Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ae3c488
add session properties changes
willronchetti Nov 5, 2025
1860242
Merge branch 'main' into download_perms
crfmc Nov 12, 2025
eca0d23
chore: bump version
crfmc Nov 12, 2025
8db142e
feat: disable checkbox when user has no access
crfmc Nov 12, 2025
35c76b3
- feat: pass down user acces object
crfmc Nov 12, 2025
982d706
feat: request userDonwloadAccess in the individual search table compo…
crfmc Nov 14, 2025
2f80d39
- feat: add support for search by property
crfmc Nov 17, 2025
e4cedc7
chore: bump version + changelog
crfmc Nov 17, 2025
a72cea4
Revert "chore: bump version + changelog"
crfmc Nov 17, 2025
a7e2bbf
Revert "- feat: add support for search by property"
crfmc Nov 17, 2025
e4f97c6
feat: add session to props for browse view
crfmc Nov 17, 2025
c5d819b
feat: update cypress user to have submission center
crfmc Nov 24, 2025
fe2864f
Merge branch 'main' into cfm-download_perms
crfmc Nov 24, 2025
eda637e
chore: bump version + changelog
crfmc Nov 24, 2025
3165f46
- feat: enable download button on search view
crfmc Nov 24, 2025
b012061
feat: update popover, add text
crfmc Dec 2, 2025
d5bdb19
feat: add link to popover, enable hover and click
crfmc Dec 8, 2025
8c74fb0
feat: add 'Tissue Subtype' field and enhance BarPlot color handling
utku-ozturk Dec 16, 2025
30c5914
feat: add 'Tissue Subtype' field and update display title in schemas
utku-ozturk Dec 16, 2025
990fcc0
fix: adjust leaf node selection and path order handling in ChartDetai…
utku-ozturk Dec 16, 2025
b51b340
- feat: pass context and session to selectAll button
crfmc Dec 22, 2025
3341ad9
Merge branch 'main' into cfm-download_perms
crfmc Dec 22, 2025
3a23608
chore: bump pyproject
crfmc Dec 22, 2025
cc3a8ae
Merge branch 'main' into utk_viz_group_by
utku-ozturk Dec 22, 2025
aa2b2f8
fix: update germ layer tissue mapping with additional entries and sta…
utku-ozturk Dec 22, 2025
668941a
feat: add tissue protocol ID retrieval and related constants
utku-ozturk Dec 22, 2025
3a7a427
feat: update tissue summary to include protocol ID and names with gro…
utku-ozturk Dec 22, 2025
56920b5
feat: update data matrix and stacked block visual to use tissue proto…
utku-ozturk Dec 22, 2025
7164138
Merge branch 'utk_viz_group_by' into cfm-download_perms
crfmc Dec 22, 2025
4c805e5
Merge branch 'main' into cfm-download_perms
crfmc Jan 2, 2026
1bba30f
chore: bump version + changelog
crfmc Jan 2, 2026
43b28a1
Merge branch 'main' into cfm-download_perms
crfmc Jan 20, 2026
cf962c9
- fix: filter currentHref in order to get subset of search params
crfmc Jan 20, 2026
abd5669
Merge branch 'main' into cfm-download_perms
crfmc Jan 26, 2026
419e17e
refactor: move logic for downloadable file count up
crfmc Jan 26, 2026
a4551a8
fix: update bug
crfmc Jan 26, 2026
fb456ac
feat: update FileSearchView component to pass down downloadableFileCount
crfmc Jan 26, 2026
b1b8fd6
Revert "Merge branch 'utk_viz_group_by' into cfm-download_perms"
crfmc Jan 26, 2026
28121ff
Merge branch 'main' into cfm-download_perms
crfmc Feb 13, 2026
5818ef3
test: add tests for file selection
crfmc Feb 13, 2026
228aa49
refactor: clean up useUserDownloadAccess hook
crfmc Feb 13, 2026
a54b810
fix: update text for select button
crfmc Feb 13, 2026
5c878cb
refactor: remove unused code, pass down isAccessResolved
crfmc Feb 13, 2026
9432010
test: add tests for file selection
crfmc Feb 13, 2026
20e5a15
test: add test for access-based selection
crfmc Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,15 @@ Change Log
* udated enum value from '[0]' to '0' for target_tissue_percentage as submitr was converting former to an array


1.9.0
=====

`PR 549: feat: Update download functionality on search and browse pages <https://github.com/smaht-dac/smaht-portal/pull/549>`_

* Update `/session-properties` to include download permission information
* Implement file selection permission checks


1.8.7
=====

Expand Down
90 changes: 87 additions & 3 deletions deploy/post_deploy_testing/cypress/e2e/03a_browse_by_file.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ const ROLE_MATRIX = {
runFacetIncludeGrouping: true,
runFacetExcludeGrouping: true,
runFacetChartBarPlotTests: true,
runBrowseFileSelectionTests: true,

expectedStatsSummaryOpts: EMPTY_STATS_SUMMARY_OPTS,
expectedNoResultsModalVisible: true,
expectedLimitedDownloadAccess: true,
},

[ROLE_TYPES.SMAHT_DBGAP]: {
Expand All @@ -62,9 +64,11 @@ const ROLE_MATRIX = {
runFacetIncludeGrouping: true,
runFacetExcludeGrouping: true,
runFacetChartBarPlotTests: true,
runBrowseFileSelectionTests: true,

expectedStatsSummaryOpts: DEFAULT_STATS_SUMMARY_OPTS,
expectedNoResultsModalVisible: false,
expectedLimitedDownloadAccess: false,
},

[ROLE_TYPES.SMAHT_NON_DBGAP]: {
Expand All @@ -79,9 +83,11 @@ const ROLE_MATRIX = {
runFacetIncludeGrouping: true,
runFacetExcludeGrouping: true,
runFacetChartBarPlotTests: true,
runBrowseFileSelectionTests: true,

expectedStatsSummaryOpts: DEFAULT_STATS_SUMMARY_OPTS,
expectedNoResultsModalVisible: false,
expectedLimitedDownloadAccess: true,
},

[ROLE_TYPES.PUBLIC_DBGAP]: {
Expand All @@ -96,9 +102,11 @@ const ROLE_MATRIX = {
runFacetIncludeGrouping: true,
runFacetExcludeGrouping: true,
runFacetChartBarPlotTests: true,
runBrowseFileSelectionTests: true,

expectedStatsSummaryOpts: EMPTY_STATS_SUMMARY_OPTS,
expectedNoResultsModalVisible: true,
expectedLimitedDownloadAccess: true,
},

[ROLE_TYPES.PUBLIC_NON_DBGAP]: {
Expand All @@ -113,9 +121,11 @@ const ROLE_MATRIX = {
runFacetIncludeGrouping: true,
runFacetExcludeGrouping: true,
runFacetChartBarPlotTests: true,
runBrowseFileSelectionTests: true,

expectedStatsSummaryOpts: EMPTY_STATS_SUMMARY_OPTS,
expectedNoResultsModalVisible: true,
expectedLimitedDownloadAccess: true,
},
};

Expand Down Expand Up @@ -475,6 +485,75 @@ function stepFacetChartBarPlotTests(caps) {
}
};

/** Browse file selection tests */
function stepBrowseFileSelectionTests(caps) {
if (caps.expectedStatsSummaryOpts.totalFiles === 0 && caps.expectedNoResultsModalVisible) {
return;
}

// For users who have limited download access
// 1. SelectAllFilesButton and the SelectAll Checkbox are enabled
cy.get('.search-view-controls-and-results #select-all-files-button').should('be.enabled');
cy.get('.search-results-outer-container .search-headers-row #select-all-checkbox[type="checkbox"]').should('be.enabled');

// 2. SelectAllFilesButton says "Select Open Access Files"
if (!caps.expectedLimitedDownloadAccess) {
cy.get('.search-view-controls-and-results #select-all-files-button')
.then(($selectAllBtn) => {
if ($selectAllBtn.hasClass('btn-secondary')) {
cy.wrap($selectAllBtn).should('have.text', 'Deselect All Files');
} else {
cy.wrap($selectAllBtn)
.should('have.text', 'Select All Files');
}
})
} else {
cy.get('.search-view-controls-and-results #select-all-files-button')
.should('have.text', 'Select Open Access Files')
.then(($selectAllBtn) => {
// 3. Hovering over the SelectAllFilesButton should show a popover
cy.wrap($selectAllBtn).trigger('mouseover');
cy.get('#select-all-files-popover').should('be.visible');
cy.wrap($selectAllBtn).trigger('mouseout');
cy.get('#select-all-files-popover').should('not.exist');
})
}

// 4. Clicking the SelectAllFilesButton should enable the Download # Selected Files button
cy.get('.search-view-controls-and-results #select-all-files-button').click();
cy.get('.search-view-controls-and-results #select-all-files-button').should('have.class', 'btn-secondary');
cy.get('.search-view-controls-and-results .right-buttons #download_tsv_multiselect').should('not.be.disabled');

cy.get('.search-view-controls-and-results .right-buttons #download_tsv_multiselect')
.then(($downloadBtn) => {
// 4a. The Download button should have the same number of files as the Access Facet (Open)
const selectedFileTotal = Number($downloadBtn.text().match(/\d+/)[0]);

// If limited download access, ensure only open files are selected
if (caps.expectedLimitedDownloadAccess) {
// Get the number of Open Access files through the access_status facet
cy.get('.search-view-controls-and-results .facets-body .facet[data-field="access_status"]')
.then(($accessStatusFacet) => {
if ($accessStatusFacet.hasClass('closed')) {
cy.wrap($accessStatusFacet).click();
}

cy.get('.search-view-controls-and-results .facets-body .facet[data-field="access_status"] .facet-list-element[data-key="Open"] a.term .facet-count')
.then(($openAccessTerm) => {
const openAccessTermText = Number($openAccessTerm.text().trim());
expect(openAccessTermText).to.equal(selectedFileTotal);
});
});
} else {
cy.get('.search-view-controls-and-results .facets-column #results-count')
.then(($resultsCount) => {
const resultsCountText = Number($resultsCount.text().trim());
expect(resultsCountText).to.equal(selectedFileTotal);
});
}
})
}

/* ----------------------------- PARAMETERIZED SUITE ----------------------------- */

const ROLES_TO_TEST = [
Expand Down Expand Up @@ -535,9 +614,14 @@ describe('Browse by role — File', () => {
stepFacetExcludeGrouping(caps);
});

it(`Facet chart bar plot tests → X-axis grouping and hover over & click "Illumina NovaSeq X Plus, Brain" bar part + popover button --> matching filtered /browse/ results (enabled: ${caps.runFacetChartBarPlotTests})`, () => {
if (!caps.runFacetChartBarPlotTests) return;
stepFacetChartBarPlotTests(caps);
// it(`Facet chart bar plot tests → X-axis grouping and hover over & click "Illumina NovaSeq X Plus, Brain" bar part + popover button --> matching filtered /browse/ results (enabled: ${caps.runFacetChartBarPlotTests})`, () => {
// if (!caps.runFacetChartBarPlotTests) return;
// stepFacetChartBarPlotTests(caps);
// });

it(`Browse file selection tests → Select files and verify counts (enabled: ${caps.runBrowseFileSelectionTests})`, () => {
if (!caps.runBrowseFileSelectionTests) return;
stepBrowseFileSelectionTests(caps);
});
});
});
Expand Down
34 changes: 33 additions & 1 deletion src/encoded/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,41 @@ def session_properties(context, request):
}
else:
raise LoginDenied(domain=request.domain)

namespace, userid = principal.split('.', 1)
properties = get_basic_properties_for_user(request, userid)
details = properties.get('details', {})
if 'admin' in details.get('groups', []):
download_perms = { # admins get everything
'open': True,
'open-early': True,
'open-network': True,
'protected': True,
'protected-early': True,
'protected-network': True,
'released': True
}
else: # add perms as we can deduce from user props
download_perms = {
'open': True,
'open-early': False,
'open-network': False,
'protected': False,
'protected-early': False,
'protected-network': False,
'released': False
}
if 'submission_centers' in details:
download_perms['open-early'] = True
download_perms['open-network'] = True
if 'dbgap' in details.get('groups', []):
download_perms['protected'] = True
download_perms['protected-early'] = True
download_perms['protected-network'] = True
if 'public-dbgap' in details.get('groups', []):
download_perms['protected'] = True
properties['download_perms'] = download_perms
print(properties)
return properties


Expand Down
94 changes: 86 additions & 8 deletions src/encoded/static/components/browse/BrowseView.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import memoize from 'memoize-one';
import _ from 'underscore';
import { Modal, OverlayTrigger, Popover } from 'react-bootstrap';
Expand Down Expand Up @@ -207,6 +207,7 @@ const BrowseFileBody = (props) => {
<BrowseViewControllerWithSelections {...props}>
<BrowseFileSearchTable
userDownloadAccess={userDownloadAccess}
isAccessResolved={isAccessResolved}
/>
</BrowseViewControllerWithSelections>
{context?.total === 0 && (
Expand Down Expand Up @@ -300,6 +301,26 @@ export class BrowseViewBody extends React.PureComponent {
}
}

/**
* @param {*} statusFacetTerms Terms for the status facet
* @param {*} userDownloadAccessObj Object containing download access for each status
* @returns number of downloadable files in the table for the current user
*/
export const getDownloadableFileCount = (
statusFacetTermCounts,
userDownloadAccessObj
) => {
// Map through the terms to get count for downloadable files
const totalDownloadableFileCount = statusFacetTermCounts?.reduce(
(acc, term) => {
const userCanDownload = userDownloadAccessObj?.[term.key];
return acc + (userCanDownload ? term.doc_count : 0);
},
0
);
return totalDownloadableFileCount;
};

export const BrowseFileSearchTable = (props) => {
const {
session,
Expand All @@ -310,15 +331,25 @@ export const BrowseFileSearchTable = (props) => {
onSelectItem,
onResetSelectedItems,
userDownloadAccess,
isAccessResolved,
} = props;
const facets = transformedFacets(context, currentAction, schemas);
const tableColumnClassName = 'results-column col';
const facetColumnClassName = 'facets-column col-auto';

const downloadableFileCount = getDownloadableFileCount(
context?.facets?.find((facet) => facet.field === 'status')?.terms || [],
userDownloadAccess
);

const selectedFileProps = {
selectedItems, // From SelectedItemsController
onSelectItem, // From SelectedItemsController
onResetSelectedItems, // From SelectedItemsController
session,
context,
userDownloadAccess,
downloadableFileCount,
};

const passProps = _.omit(props, 'isFullscreen', 'toggleFullScreen');
Expand All @@ -327,11 +358,14 @@ export const BrowseFileSearchTable = (props) => {
const aboveTableComponent = (
<BrowseViewAboveSearchTableControls
topLeftChildren={
<SelectAllFilesButton {...selectedFileProps} {...{ context }} />
<SelectAllFilesButton
{...selectedFileProps}
{...{ session, context }}
/>
}>
<div className="d-flex gap-2">
<DonorMetadataDownloadButton session={session} />
{userDownloadAccess?.['protected'] ? (
{userDownloadAccess?.['open'] && downloadableFileCount > 0 ? (
<SelectedItemsDownloadButton
id="download_tsv_multiselect"
disabled={selectedItems.size === 0}
Expand Down Expand Up @@ -505,13 +539,43 @@ const TypeColumnTitlePopover = function (props) {
);
};

const CustomColTitle = ({
session, // pass down session information
selectedItems,
onSelectItem,
onResetSelectedItems,
context,
userDownloadAccess,
downloadableFileCount,
}) => {
// Context now passed in from HeadersRowColumn (for file count)
return (
<SelectAllFilesButton
{...{
session, // pass down session information
selectedItems,
onSelectItem,
onResetSelectedItems,
context,
userDownloadAccess,
downloadableFileCount,
}}
type="checkbox"
/>
);
};

/**
* A column extension map specifically for browse view file tables.
*/
export function createBrowseFileColumnExtensionMap({
selectedItems,
onSelectItem,
onResetSelectedItems,
session,
context,
userDownloadAccess,
downloadableFileCount,
}) {
const columnExtensionMap = {
...originalColExtMap, // Pull in defaults for all tables
Expand All @@ -522,21 +586,35 @@ export function createBrowseFileColumnExtensionMap({
// Select all button
'@type': {
colTitle: (
// Context now passed in from HeadersRowColumn (for file count)
<SelectAllFilesButton
{...{ selectedItems, onSelectItem, onResetSelectedItems }}
type="checkbox"
<CustomColTitle
selectedItems={selectedItems}
onSelectItem={onSelectItem}
onResetSelectedItems={onResetSelectedItems}
session={session}
context={context}
userDownloadAccess={userDownloadAccess}
downloadableFileCount={downloadableFileCount}
/>
),
hideTooltip: true,
noSort: true,
widthMap: { lg: 60, md: 60, sm: 60 },
render: (result, parentProps) => {
return (
const userHasDownloadAccess =
parentProps?.userDownloadAccess?.[result?.status];

return userHasDownloadAccess ? (
<SelectionItemCheckbox
{...{ selectedItems, onSelectItem, result }}
isMultiSelect={true}
/>
) : (
<input
type="checkbox"
data-tip="You do not have access to download this item"
disabled="disabled"
className="me-2"
/>
);
},
},
Expand Down
Loading
Loading