Skip to content

Commit 10bd890

Browse files
authored
[CORE-561] Show Resources under NIH RAS Auth Service (#5359)
1 parent b895fcb commit 10bd890

File tree

5 files changed

+260
-55
lines changed

5 files changed

+260
-55
lines changed

src/libs/ajax/User.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,18 @@ export const User = (signal?: AbortSignal) => {
292292
return res.json();
293293
},
294294

295+
getNihResources: async (): Promise<OrchestrationNihStatusResponse | undefined> => {
296+
try {
297+
const res = await fetchOrchestration('api/nih/resources', _.merge(authOpts(), { signal }));
298+
return res.json();
299+
} catch (error: unknown) {
300+
if (error instanceof Response && error.status === 404) {
301+
return;
302+
}
303+
throw error;
304+
}
305+
},
306+
295307
getNihStatus: async (): Promise<OrchestrationNihStatusResponse | undefined> => {
296308
try {
297309
const res = await fetchOrchestration('api/nih/status', _.merge(authOpts(), { signal }));

src/profile/external-identities/NihAccount.ts

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
1-
import { icon, Modal } from '@terra-ui-packages/components';
1+
import { Modal } from '@terra-ui-packages/components';
22
import _ from 'lodash/fp';
33
import { Fragment, useState } from 'react';
44
import { div, h, h3, span } from 'react-hyperscript-helpers';
5-
import Collapse from 'src/components/Collapse';
65
import { ButtonPrimary, Link, spinnerOverlay } from 'src/components/common';
76
import { InfoBox } from 'src/components/InfoBox';
8-
import { User } from 'src/libs/ajax/User';
9-
import { NihDatasetPermission } from 'src/libs/ajax/User';
7+
import { NihDatasetPermission, User } from 'src/libs/ajax/User';
108
import { withErrorReporting } from 'src/libs/error';
119
import * as Nav from 'src/libs/nav';
1210
import { notify } from 'src/libs/notifications';
1311
import { useOnMount, useStore } from 'src/libs/react-utils';
1412
import { AuthState, authStore } from 'src/libs/state';
1513
import * as Utils from 'src/libs/utils';
1614
import { linkStyles as styles } from 'src/profile/external-identities/LinkStyles';
15+
import { NihResources } from 'src/profile/external-identities/NihResources';
1716
import { ShibbolethLink } from 'src/profile/external-identities/ShibbolethLink';
1817
import { SpacedSpinner } from 'src/profile/SpacedSpinner';
1918

@@ -39,7 +38,7 @@ export const NihAccount = ({ nihToken }) => {
3938
});
4039

4140
if (nihToken) {
42-
// Clear the query string, but use replace so the back button doesn't take the user back to the token
41+
// Clear the query string, but use "replace" so the back button doesn't take the user back to the token
4342
Nav.history.replace({ search: 'tab=externalIdentities' });
4443
linkNihAccount();
4544
}
@@ -94,55 +93,7 @@ export const NihAccount = ({ nihToken }) => {
9493
])
9594
),
9695
]),
97-
isLinked &&
98-
div({ style: styles.idLink.linkContentBottom }, [
99-
h3({ style: { fontWeight: 500, margin: 0 } }, ['Resources']),
100-
!_.isEmpty(authorizedDatasets) &&
101-
h(
102-
Collapse,
103-
{
104-
style: { marginTop: '1rem' },
105-
title: 'Authorized to access',
106-
titleFirst: true,
107-
initialOpenState: true,
108-
},
109-
[
110-
div({ style: { marginTop: '0.5rem' } }, [
111-
_.map(({ name }) => div({ key: name, style: { lineHeight: '24px' } }, [name]), authorizedDatasets),
112-
]),
113-
]
114-
),
115-
!_.isEmpty(unauthorizedDatasets) &&
116-
h(
117-
Collapse,
118-
{
119-
style: { marginTop: '1rem' },
120-
title: 'Not authorized',
121-
titleFirst: true,
122-
afterTitle: h(InfoBox, [
123-
'Your account was linked, but you are not authorized to view these controlled datasets. ',
124-
'If you think you should have access, please ',
125-
h(
126-
Link,
127-
{
128-
href: 'https://dbgap.ncbi.nlm.nih.gov/aa/wga.cgi?page=login',
129-
...Utils.newTabLinkProps,
130-
},
131-
[
132-
'verify your credentials here',
133-
icon('pop-out', { size: 12, style: { marginLeft: '0.2rem', verticalAlign: 'baseline' } }),
134-
]
135-
),
136-
'.',
137-
]),
138-
},
139-
[
140-
div({ style: { marginTop: '0.5rem' } }, [
141-
_.map(({ name }) => div({ key: name, style: { lineHeight: '24px' } }, [name]), unauthorizedDatasets),
142-
]),
143-
]
144-
),
145-
]),
96+
isLinked && h(NihResources, { authorizedDatasets, unauthorizedDatasets }),
14697
isConfirmUnlinkModalOpen &&
14798
h(
14899
Modal,
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { fireEvent, screen } from '@testing-library/react';
2+
import React from 'react';
3+
import { NihDatasetPermission } from 'src/libs/ajax/User';
4+
import { renderWithAppContexts as render } from 'src/testing/test-utils';
5+
6+
import { NihResources } from './NihResources';
7+
8+
const mockAuthorized: NihDatasetPermission[] = [
9+
{ name: 'Dataset A', authorized: true },
10+
{ name: 'Dataset B', authorized: true },
11+
];
12+
13+
const mockUnauthorized: NihDatasetPermission[] = [
14+
{ name: 'Dataset C', authorized: false },
15+
{ name: 'Dataset D', authorized: false },
16+
];
17+
18+
describe('NihResources', () => {
19+
describe('Component Structure', () => {
20+
it('should display the Resources header', () => {
21+
// Arrange
22+
const props = { authorizedDatasets: [], unauthorizedDatasets: [] };
23+
24+
// Act
25+
render(<NihResources {...props} />);
26+
27+
// Assert
28+
expect(screen.getByText('Resources')).toBeInTheDocument();
29+
});
30+
});
31+
32+
describe('Authorized Datasets Section', () => {
33+
it('should display authorized datasets section when datasets are provided', () => {
34+
// Arrange
35+
const props = { authorizedDatasets: mockAuthorized, unauthorizedDatasets: [] };
36+
37+
// Act
38+
render(<NihResources {...props} />);
39+
40+
// Assert
41+
expect(screen.getByText('Authorized to access')).toBeInTheDocument();
42+
mockAuthorized.forEach(({ name }) => {
43+
expect(screen.getByText(name)).toBeInTheDocument();
44+
});
45+
});
46+
47+
it('should not display authorized datasets section when no datasets are provided', () => {
48+
// Arrange
49+
const props = { authorizedDatasets: [], unauthorizedDatasets: [] };
50+
51+
// Act
52+
render(<NihResources {...props} />);
53+
54+
// Assert
55+
expect(screen.queryByText('Authorized to access')).not.toBeInTheDocument();
56+
});
57+
});
58+
59+
describe('Unauthorized Datasets Section', () => {
60+
it('should display unauthorized datasets section header but hide content by default', () => {
61+
// Arrange
62+
const props = { authorizedDatasets: [], unauthorizedDatasets: mockUnauthorized };
63+
64+
// Act
65+
render(<NihResources {...props} />);
66+
67+
// Assert
68+
expect(screen.getByText('Not authorized')).toBeInTheDocument();
69+
mockUnauthorized.forEach(({ name }) => {
70+
expect(screen.queryByText(name)).not.toBeInTheDocument();
71+
});
72+
});
73+
74+
it('should reveal unauthorized datasets when section header is clicked', () => {
75+
// Arrange
76+
const props = { authorizedDatasets: [], unauthorizedDatasets: mockUnauthorized };
77+
render(<NihResources {...props} />);
78+
79+
// Act
80+
fireEvent.click(screen.getByText('Not authorized'));
81+
82+
// Assert
83+
mockUnauthorized.forEach(({ name }) => {
84+
expect(screen.getByText(name)).toBeInTheDocument();
85+
});
86+
});
87+
88+
it('should not display unauthorized datasets section when no datasets are provided', () => {
89+
// Arrange
90+
const props = { authorizedDatasets: [], unauthorizedDatasets: [] };
91+
92+
// Act
93+
render(<NihResources {...props} />);
94+
95+
// Assert
96+
expect(screen.queryByText('Not authorized')).not.toBeInTheDocument();
97+
});
98+
});
99+
100+
describe('Combined Sections Behavior', () => {
101+
it('should display both sections independently with correct initial states', () => {
102+
// Arrange
103+
const props = { authorizedDatasets: mockAuthorized, unauthorizedDatasets: mockUnauthorized };
104+
105+
// Act
106+
render(<NihResources {...props} />);
107+
108+
// Assert - the Authorized section is expanded by default
109+
expect(screen.getByText('Authorized to access')).toBeInTheDocument();
110+
mockAuthorized.forEach(({ name }) => {
111+
expect(screen.getByText(name)).toBeInTheDocument();
112+
});
113+
114+
// Assert - Unauthorized section is collapsed by default
115+
expect(screen.getByText('Not authorized')).toBeInTheDocument();
116+
mockUnauthorized.forEach(({ name }) => {
117+
expect(screen.queryByText(name)).not.toBeInTheDocument();
118+
});
119+
});
120+
121+
it('should toggle unauthorized section independently of authorized section', () => {
122+
// Arrange
123+
const props = { authorizedDatasets: mockAuthorized, unauthorizedDatasets: mockUnauthorized };
124+
render(<NihResources {...props} />);
125+
126+
// Act
127+
fireEvent.click(screen.getByText('Not authorized'));
128+
129+
// Assert - the Authorized section remains visible
130+
mockAuthorized.forEach(({ name }) => {
131+
expect(screen.getByText(name)).toBeInTheDocument();
132+
});
133+
134+
// Assert - the Unauthorized section is now visible
135+
mockUnauthorized.forEach(({ name }) => {
136+
expect(screen.getByText(name)).toBeInTheDocument();
137+
});
138+
});
139+
});
140+
141+
describe('Empty State', () => {
142+
it('should only display header when no datasets are provided', () => {
143+
// Arrange
144+
const props = { authorizedDatasets: [], unauthorizedDatasets: [] };
145+
146+
// Act
147+
render(<NihResources {...props} />);
148+
149+
// Assert
150+
expect(screen.getByText('Resources')).toBeInTheDocument();
151+
expect(screen.queryByText('Authorized to access')).not.toBeInTheDocument();
152+
expect(screen.queryByText('Not authorized')).not.toBeInTheDocument();
153+
});
154+
});
155+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { icon } from '@terra-ui-packages/components';
2+
import _ from 'lodash/fp';
3+
import React from 'react';
4+
import Collapse from 'src/components/Collapse';
5+
import { Link } from 'src/components/common';
6+
import { InfoBox } from 'src/components/InfoBox';
7+
import { NihDatasetPermission } from 'src/libs/ajax/User';
8+
import * as Utils from 'src/libs/utils';
9+
import { linkStyles as styles } from 'src/profile/external-identities/LinkStyles';
10+
11+
interface NihResourcesProps {
12+
authorizedDatasets: NihDatasetPermission[];
13+
unauthorizedDatasets: NihDatasetPermission[];
14+
}
15+
16+
export const NihResources: React.FC<NihResourcesProps> = ({ authorizedDatasets, unauthorizedDatasets }) => {
17+
return (
18+
<div style={styles.idLink.linkContentBottom}>
19+
<h3 style={{ fontWeight: 500, margin: 0 }}>Resources</h3>
20+
{!_.isEmpty(authorizedDatasets) && (
21+
<Collapse style={{ marginTop: '1rem' }} title='Authorized to access' titleFirst initialOpenState>
22+
<div style={{ marginTop: '0.5rem' }}>
23+
{_.map(
24+
({ name }) => (
25+
<div key={name} style={{ lineHeight: '24px' }}>
26+
{name}
27+
</div>
28+
),
29+
authorizedDatasets
30+
)}
31+
</div>
32+
</Collapse>
33+
)}
34+
{!_.isEmpty(unauthorizedDatasets) && (
35+
<Collapse
36+
style={{ marginTop: '1rem' }}
37+
title='Not authorized'
38+
titleFirst
39+
afterTitle={
40+
<InfoBox>
41+
Your account was linked, but you are not authorized to view these controlled datasets. If you think you
42+
should have access, please{' '}
43+
<Link href='https://dbgap.ncbi.nlm.nih.gov/aa/wga.cgi?page=login' {...Utils.newTabLinkProps}>
44+
verify your credentials here
45+
{icon('pop-out', { size: 12, style: { marginLeft: '0.2rem', verticalAlign: 'baseline' } })}
46+
</Link>
47+
.
48+
</InfoBox>
49+
}
50+
>
51+
<div style={{ marginTop: '0.5rem' }}>
52+
{_.map(
53+
({ name }) => (
54+
<div key={name} style={{ lineHeight: '24px' }}>
55+
{name}
56+
</div>
57+
),
58+
unauthorizedDatasets
59+
)}
60+
</div>
61+
</Collapse>
62+
)}
63+
</div>
64+
);
65+
};

0 commit comments

Comments
 (0)