Skip to content

Commit 1258f4b

Browse files
authored
Fix PivotItem/SecuredByRole render and workspace cost reloading (#3739)
* Nesting of PivotItems in SecuredByRole causes issues Fixes #3738 * Update UI version
1 parent 13ad442 commit 1258f4b

6 files changed

Lines changed: 77 additions & 74 deletions

File tree

ui/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "tre-ui",
3-
"version": "0.5.9",
3+
"version": "0.5.10",
44
"private": true,
55
"dependencies": {
66
"@azure/msal-browser": "^2.35.0",

ui/app/src/components/shared/CostsTag.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const CostsTag: React.FunctionComponent<CostsTagProps> = (props: CostsTag
2525
costs = workspaceCtx.costs;
2626
} else if (costsCtx.costs.length > 0) {
2727
costs = costsCtx.costs;
28-
} else {
28+
} else if(!workspaceCtx.workspace.id) {
2929
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${props.resourceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
3030
const r = await apiCall(`${ApiEndpoint.Workspaces}/${props.resourceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
3131
costs = [{costs: r.costs, id: r.id, name: r.name }];
@@ -44,11 +44,11 @@ export const CostsTag: React.FunctionComponent<CostsTagProps> = (props: CostsTag
4444
maximumFractionDigits: 2
4545
}).format(resourceCosts.costs[0].cost);
4646
setFormattedCost(formattedCost);
47-
setLoadingState(LoadingState.Ok);
4847
}
48+
setLoadingState(LoadingState.Ok);
4949
}
5050
fetchCostData();
51-
}, [apiCall, costsCtx.loadingState, props.resourceId, workspaceCtx.costs, costsCtx.costs]);
51+
}, [apiCall, props.resourceId, workspaceCtx.costs, costsCtx.costs, workspaceCtx.workspace.id]);
5252

5353
const costBadge = (
5454
<Stack.Item style={{ maxHeight: 18 }} className="tre-badge">

ui/app/src/components/shared/ResourceBody.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ export const ResourceBody: React.FunctionComponent<ResourceBodyProps> = (props:
6262
}
6363
{
6464
!props.readonly &&
65-
<SecuredByRole allowedAppRoles={historyRoles} allowedWorkspaceRoles={historyRoles} workspaceId={workspaceId} element={
66-
<PivotItem headerText="History">
65+
<PivotItem headerText="History">
66+
<SecuredByRole allowedAppRoles={historyRoles} allowedWorkspaceRoles={historyRoles} workspaceId={workspaceId} errorString={`Must have ${historyRoles.join(" or ")} role`} element={
6767
<ResourceHistoryList resource={props.resource} />
68-
</PivotItem>
69-
} />
68+
} />
69+
</PivotItem>
7070
}
7171
{
7272
!props.readonly &&
73-
<SecuredByRole allowedAppRoles={operationsRoles} allowedWorkspaceRoles={operationsRoles} workspaceId={workspaceId} element={
74-
<PivotItem headerText="Operations">
73+
<PivotItem headerText="Operations">
74+
<SecuredByRole allowedAppRoles={operationsRoles} allowedWorkspaceRoles={operationsRoles} workspaceId={workspaceId} errorString={`Must have ${operationsRoles.join(" or ")} role`} element={
7575
<ResourceOperationsList resource={props.resource} />
76-
</PivotItem>
77-
} />
76+
} />
77+
</PivotItem>
7878
}
7979
</Pivot>
8080
);

ui/app/src/components/shared/ResourceCard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,6 @@ export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props:
149149
</Stack.Item>
150150
</Stack>
151151
</Stack.Item>
152-
{console.log("costTagsToles", costsTagsRoles)}
153152
<SecuredByRole allowedAppRoles={costsTagsRoles} allowedWorkspaceRoles={costsTagsRoles} workspaceId={workspaceId} element={
154153
<CostsTag resourceId={props.resource.id} />
155154
}
Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useContext, useEffect, useRef, useState } from 'react';
1+
import React, { useContext, useEffect, useState } from 'react';
22
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
33
import { AppRolesContext } from '../../contexts/AppRolesContext';
44
import { MessageBar, MessageBarType } from '@fluentui/react';
@@ -18,44 +18,42 @@ export const SecuredByRole: React.FunctionComponent<SecuredByRoleProps> = (props
1818
const apiCall = useAuthApiCall();
1919

2020
const appRoles = useContext(AppRolesContext);
21-
const workspaceCtx = useRef(useContext(WorkspaceContext));
22-
let [workspaceRoles, setRoles] = useState([] as Array<string>);
21+
const workspaceCtx = useContext(WorkspaceContext);
22+
const [workspaceRoles, setRoles] = useState([] as Array<string>);
2323

2424
useEffect(() => {
2525
const getWorkspaceRoles = async () => {
26-
if (!workspaceCtx.current.workspace.id && props.workspaceId !== "") {
27-
let workspaceRoles = [] as Array<string>;
26+
if (!workspaceCtx.workspace.id && props.workspaceId !== "") {
27+
let r = [] as Array<string>;
2828

2929
let workspaceAuth = (await apiCall(`${ApiEndpoint.Workspaces}/${props.workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth;
3030
if (workspaceAuth) {
3131
await apiCall(`${ApiEndpoint.Workspaces}/${props.workspaceId}`, HttpMethod.Get, workspaceAuth.scopeId,
32-
undefined, ResultType.JSON, (roles: Array<string>) => {
33-
workspaceRoles = roles;
34-
}, true);
32+
undefined, ResultType.JSON, (roles: Array<string>) => {
33+
r = roles;
34+
}, true);
3535
}
36-
setRoles(workspaceRoles);
36+
setRoles(r);
3737
}
3838
};
3939

40-
if (workspaceCtx.current.roles.length === 0 && props.workspaceId !== undefined){
40+
if (workspaceCtx.roles.length === 0 && props.workspaceId !== undefined) {
4141
getWorkspaceRoles();
4242
}
4343
else {
44-
setRoles(workspaceCtx.current.roles);
44+
setRoles(workspaceCtx.roles);
4545
}
4646

47-
}, [apiCall, workspaceCtx.current.workspace.id , props.workspaceId, workspaceCtx.current.roles]);
48-
49-
if (workspaceRoles.some(x => props.allowedWorkspaceRoles?.includes(x))) return props.element;
50-
51-
if (appRoles.roles.some(x => props.allowedAppRoles?.includes(x))) return props.element;
52-
53-
return props.errorString ? (
54-
<MessageBar messageBarType={MessageBarType.error} isMultiline={true}>
55-
<h3>Access Denied</h3>
56-
<p>{props.errorString}</p>
57-
</MessageBar>
58-
) : (
59-
<></>
47+
}, [apiCall, workspaceCtx.workspace.id, props.workspaceId, workspaceCtx.roles]);
48+
49+
return (
50+
(workspaceRoles.some(x => props.allowedWorkspaceRoles?.includes(x)) || appRoles.roles.some(x => props.allowedAppRoles?.includes(x)))
51+
? props.element
52+
: (props.errorString && (workspaceRoles.length > 0 || appRoles.roles.length > 0)
53+
? <MessageBar messageBarType={MessageBarType.error} isMultiline={true}>
54+
<h3>Access Denied</h3>
55+
<p>{props.errorString}</p>
56+
</MessageBar>
57+
: null)
6058
);
6159
};

ui/app/src/components/workspaces/WorkspaceProvider.tsx

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
2727
const [workspaceServices, setWorkspaceServices] = useState([] as Array<WorkspaceService>);
2828
const [sharedServices, setSharedServices] = useState([] as Array<SharedService>);
2929
const workspaceCtx = useRef(useContext(WorkspaceContext));
30+
const [wsRoles, setWSRoles] = useState([] as Array<string>);
3031
const [loadingState, setLoadingState] = useState(LoadingState.Loading);
3132
const [apiError, setApiError] = useState({} as APIError);
3233
const { workspaceId } = useParams();
@@ -37,41 +38,6 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
3738

3839
// set workspace context from url
3940
useEffect(() => {
40-
const getWorkspaceCosts = async () => {
41-
try {
42-
// TODO: amend when costs enabled in API for WorkspaceRoleName.Researcher
43-
if(workspaceCtx.current.roles.includes(WorkspaceRoleName.WorkspaceOwner)){
44-
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
45-
const r = await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
46-
const costs = [
47-
...r.costs,
48-
...r.workspace_services,
49-
...r.workspace_services.flatMap((ws: { user_resources: any; }) => [
50-
...ws.user_resources
51-
])
52-
];
53-
workspaceCtx.current.setCosts(costs);
54-
}
55-
}
56-
catch (e: any) {
57-
if (e instanceof APIError) {
58-
if (e.status === 404 /*subscription not supported*/) {
59-
}
60-
else if (e.status === 429 /*too many requests*/ || e.status === 503 /*service unavaiable*/) {
61-
let msg = JSON.parse(e.message);
62-
let retryAfter = Number(msg.error["retry-after"]);
63-
setTimeout(getWorkspaceCosts, retryAfter * 1000);
64-
}
65-
else {
66-
e.userMessage = 'Error retrieving costs';
67-
}
68-
}
69-
else {
70-
e.userMessage = 'Error retrieving costs';
71-
}
72-
setCostApiError(e);
73-
}
74-
};
7541

7642
const getWorkspace = async () => {
7743
try {
@@ -95,16 +61,16 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
9561
ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get, scopeId)).workspace;
9662
workspaceCtx.current.setWorkspace(ws);
9763
workspaceCtx.current.setRoles(wsRoles);
64+
setWSRoles(wsRoles);
9865

9966
// get workspace services to pass to nav + ws services page
10067
const workspaceServices = await apiCall(`${ApiEndpoint.Workspaces}/${ws.id}/${ApiEndpoint.WorkspaceServices}`,
10168
HttpMethod.Get, ws.properties.scope_id);
10269
setWorkspaceServices(workspaceServices.workspaceServices);
103-
setLoadingState(LoadingState.Ok);
10470
// get shared services to pass to nav shared services pages
10571
const sharedServices = await apiCall(ApiEndpoint.SharedServices, HttpMethod.Get);
10672
setSharedServices(sharedServices.sharedServices);
107-
getWorkspaceCosts();
73+
setLoadingState(LoadingState.Ok);
10874
} else if (appRoles.roles.includes(RoleName.TREAdmin)) {
10975
ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get)).workspace;
11076
workspaceCtx.current.setWorkspace(ws);
@@ -140,6 +106,46 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
140106
};
141107
}, [apiCall, workspaceId, isTREAdminUser, appRoles.roles]);
142108

109+
useEffect(() => {
110+
const getWorkspaceCosts = async () => {
111+
try {
112+
// TODO: amend when costs enabled in API for WorkspaceRoleName.Researcher
113+
if(wsRoles.includes(WorkspaceRoleName.WorkspaceOwner)){
114+
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
115+
const r = await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
116+
const costs = [
117+
...r.costs,
118+
...r.workspace_services,
119+
...r.workspace_services.flatMap((ws: { user_resources: any; }) => [
120+
...ws.user_resources
121+
])
122+
];
123+
workspaceCtx.current.setCosts(costs);
124+
}
125+
}
126+
catch (e: any) {
127+
if (e instanceof APIError) {
128+
if (e.status === 404 /*subscription not supported*/) {
129+
}
130+
else if (e.status === 429 /*too many requests*/ || e.status === 503 /*service unavaiable*/) {
131+
let msg = JSON.parse(e.message);
132+
let retryAfter = Number(msg.error["retry-after"]);
133+
setTimeout(getWorkspaceCosts, retryAfter * 1000);
134+
}
135+
else {
136+
e.userMessage = 'Error retrieving costs';
137+
}
138+
}
139+
else {
140+
e.userMessage = 'Error retrieving costs';
141+
}
142+
setCostApiError(e);
143+
}
144+
};
145+
146+
getWorkspaceCosts();
147+
},[apiCall, workspaceId, wsRoles]);
148+
143149
const addWorkspaceService = (w: WorkspaceService) => {
144150
let ws = [...workspaceServices];
145151
ws.push(w);

0 commit comments

Comments
 (0)