Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix PivotItem/SecuredByRole render and workspace cost reloading #3739

Merged
merged 2 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion ui/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tre-ui",
"version": "0.5.9",
"version": "0.5.10",
"private": true,
"dependencies": {
"@azure/msal-browser": "^2.35.0",
Expand Down
6 changes: 3 additions & 3 deletions ui/app/src/components/shared/CostsTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const CostsTag: React.FunctionComponent<CostsTagProps> = (props: CostsTag
costs = workspaceCtx.costs;
} else if (costsCtx.costs.length > 0) {
costs = costsCtx.costs;
} else {
} else if(!workspaceCtx.workspace.id) {
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${props.resourceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
const r = await apiCall(`${ApiEndpoint.Workspaces}/${props.resourceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
costs = [{costs: r.costs, id: r.id, name: r.name }];
Expand All @@ -44,11 +44,11 @@ export const CostsTag: React.FunctionComponent<CostsTagProps> = (props: CostsTag
maximumFractionDigits: 2
}).format(resourceCosts.costs[0].cost);
setFormattedCost(formattedCost);
setLoadingState(LoadingState.Ok);
}
setLoadingState(LoadingState.Ok);
}
fetchCostData();
}, [apiCall, costsCtx.loadingState, props.resourceId, workspaceCtx.costs, costsCtx.costs]);
}, [apiCall, props.resourceId, workspaceCtx.costs, costsCtx.costs, workspaceCtx.workspace.id]);

const costBadge = (
<Stack.Item style={{ maxHeight: 18 }} className="tre-badge">
Expand Down
16 changes: 8 additions & 8 deletions ui/app/src/components/shared/ResourceBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,19 @@ export const ResourceBody: React.FunctionComponent<ResourceBodyProps> = (props:
}
{
!props.readonly &&
<SecuredByRole allowedAppRoles={historyRoles} allowedWorkspaceRoles={historyRoles} workspaceId={workspaceId} element={
<PivotItem headerText="History">
<PivotItem headerText="History">
<SecuredByRole allowedAppRoles={historyRoles} allowedWorkspaceRoles={historyRoles} workspaceId={workspaceId} errorString={`Must have ${historyRoles.join(" or ")} role`} element={
<ResourceHistoryList resource={props.resource} />
</PivotItem>
} />
} />
</PivotItem>
}
{
!props.readonly &&
<SecuredByRole allowedAppRoles={operationsRoles} allowedWorkspaceRoles={operationsRoles} workspaceId={workspaceId} element={
<PivotItem headerText="Operations">
<PivotItem headerText="Operations">
<SecuredByRole allowedAppRoles={operationsRoles} allowedWorkspaceRoles={operationsRoles} workspaceId={workspaceId} errorString={`Must have ${operationsRoles.join(" or ")} role`} element={
<ResourceOperationsList resource={props.resource} />
</PivotItem>
} />
} />
</PivotItem>
}
</Pivot>
);
Expand Down
1 change: 0 additions & 1 deletion ui/app/src/components/shared/ResourceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ export const ResourceCard: React.FunctionComponent<ResourceCardProps> = (props:
</Stack.Item>
</Stack>
</Stack.Item>
{console.log("costTagsToles", costsTagsRoles)}
<SecuredByRole allowedAppRoles={costsTagsRoles} allowedWorkspaceRoles={costsTagsRoles} workspaceId={workspaceId} element={
<CostsTag resourceId={props.resource.id} />
}
Expand Down
46 changes: 22 additions & 24 deletions ui/app/src/components/shared/SecuredByRole.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { WorkspaceContext } from '../../contexts/WorkspaceContext';
import { AppRolesContext } from '../../contexts/AppRolesContext';
import { MessageBar, MessageBarType } from '@fluentui/react';
Expand All @@ -18,44 +18,42 @@ export const SecuredByRole: React.FunctionComponent<SecuredByRoleProps> = (props
const apiCall = useAuthApiCall();

const appRoles = useContext(AppRolesContext);
const workspaceCtx = useRef(useContext(WorkspaceContext));
let [workspaceRoles, setRoles] = useState([] as Array<string>);
const workspaceCtx = useContext(WorkspaceContext);
const [workspaceRoles, setRoles] = useState([] as Array<string>);

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

let workspaceAuth = (await apiCall(`${ApiEndpoint.Workspaces}/${props.workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth;
if (workspaceAuth) {
await apiCall(`${ApiEndpoint.Workspaces}/${props.workspaceId}`, HttpMethod.Get, workspaceAuth.scopeId,
undefined, ResultType.JSON, (roles: Array<string>) => {
workspaceRoles = roles;
}, true);
undefined, ResultType.JSON, (roles: Array<string>) => {
r = roles;
}, true);
}
setRoles(workspaceRoles);
setRoles(r);
}
};

if (workspaceCtx.current.roles.length === 0 && props.workspaceId !== undefined){
if (workspaceCtx.roles.length === 0 && props.workspaceId !== undefined) {
getWorkspaceRoles();
}
else {
setRoles(workspaceCtx.current.roles);
setRoles(workspaceCtx.roles);
}

}, [apiCall, workspaceCtx.current.workspace.id , props.workspaceId, workspaceCtx.current.roles]);

if (workspaceRoles.some(x => props.allowedWorkspaceRoles?.includes(x))) return props.element;

if (appRoles.roles.some(x => props.allowedAppRoles?.includes(x))) return props.element;

return props.errorString ? (
<MessageBar messageBarType={MessageBarType.error} isMultiline={true}>
<h3>Access Denied</h3>
<p>{props.errorString}</p>
</MessageBar>
) : (
<></>
}, [apiCall, workspaceCtx.workspace.id, props.workspaceId, workspaceCtx.roles]);

return (
(workspaceRoles.some(x => props.allowedWorkspaceRoles?.includes(x)) || appRoles.roles.some(x => props.allowedAppRoles?.includes(x)))
? props.element
: (props.errorString && (workspaceRoles.length > 0 || appRoles.roles.length > 0)
? <MessageBar messageBarType={MessageBarType.error} isMultiline={true}>
<h3>Access Denied</h3>
<p>{props.errorString}</p>
</MessageBar>
: null)
);
};
80 changes: 43 additions & 37 deletions ui/app/src/components/workspaces/WorkspaceProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
const [workspaceServices, setWorkspaceServices] = useState([] as Array<WorkspaceService>);
const [sharedServices, setSharedServices] = useState([] as Array<SharedService>);
const workspaceCtx = useRef(useContext(WorkspaceContext));
const [wsRoles, setWSRoles] = useState([] as Array<string>);
const [loadingState, setLoadingState] = useState(LoadingState.Loading);
const [apiError, setApiError] = useState({} as APIError);
const { workspaceId } = useParams();
Expand All @@ -37,41 +38,6 @@ export const WorkspaceProvider: React.FunctionComponent = () => {

// set workspace context from url
useEffect(() => {
const getWorkspaceCosts = async () => {
try {
// TODO: amend when costs enabled in API for WorkspaceRoleName.Researcher
if(workspaceCtx.current.roles.includes(WorkspaceRoleName.WorkspaceOwner)){
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
const r = await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
const costs = [
...r.costs,
...r.workspace_services,
...r.workspace_services.flatMap((ws: { user_resources: any; }) => [
...ws.user_resources
])
];
workspaceCtx.current.setCosts(costs);
}
}
catch (e: any) {
if (e instanceof APIError) {
if (e.status === 404 /*subscription not supported*/) {
}
else if (e.status === 429 /*too many requests*/ || e.status === 503 /*service unavaiable*/) {
let msg = JSON.parse(e.message);
let retryAfter = Number(msg.error["retry-after"]);
setTimeout(getWorkspaceCosts, retryAfter * 1000);
}
else {
e.userMessage = 'Error retrieving costs';
}
}
else {
e.userMessage = 'Error retrieving costs';
}
setCostApiError(e);
}
};

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

// get workspace services to pass to nav + ws services page
const workspaceServices = await apiCall(`${ApiEndpoint.Workspaces}/${ws.id}/${ApiEndpoint.WorkspaceServices}`,
HttpMethod.Get, ws.properties.scope_id);
setWorkspaceServices(workspaceServices.workspaceServices);
setLoadingState(LoadingState.Ok);
// get shared services to pass to nav shared services pages
const sharedServices = await apiCall(ApiEndpoint.SharedServices, HttpMethod.Get);
setSharedServices(sharedServices.sharedServices);
getWorkspaceCosts();
setLoadingState(LoadingState.Ok);
} else if (appRoles.roles.includes(RoleName.TREAdmin)) {
ws = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}`, HttpMethod.Get)).workspace;
workspaceCtx.current.setWorkspace(ws);
Expand Down Expand Up @@ -140,6 +106,46 @@ export const WorkspaceProvider: React.FunctionComponent = () => {
};
}, [apiCall, workspaceId, isTREAdminUser, appRoles.roles]);

useEffect(() => {
const getWorkspaceCosts = async () => {
try {
// TODO: amend when costs enabled in API for WorkspaceRoleName.Researcher
if(wsRoles.includes(WorkspaceRoleName.WorkspaceOwner)){
let scopeId = (await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/scopeid`, HttpMethod.Get)).workspaceAuth.scopeId;
const r = await apiCall(`${ApiEndpoint.Workspaces}/${workspaceId}/${ApiEndpoint.Costs}`, HttpMethod.Get, scopeId, undefined, ResultType.JSON);
const costs = [
...r.costs,
...r.workspace_services,
...r.workspace_services.flatMap((ws: { user_resources: any; }) => [
...ws.user_resources
])
];
workspaceCtx.current.setCosts(costs);
}
}
catch (e: any) {
if (e instanceof APIError) {
if (e.status === 404 /*subscription not supported*/) {
}
else if (e.status === 429 /*too many requests*/ || e.status === 503 /*service unavaiable*/) {
let msg = JSON.parse(e.message);
let retryAfter = Number(msg.error["retry-after"]);
setTimeout(getWorkspaceCosts, retryAfter * 1000);
}
else {
e.userMessage = 'Error retrieving costs';
}
}
else {
e.userMessage = 'Error retrieving costs';
}
setCostApiError(e);
}
};

getWorkspaceCosts();
},[apiCall, workspaceId, wsRoles]);

const addWorkspaceService = (w: WorkspaceService) => {
let ws = [...workspaceServices];
ws.push(w);
Expand Down
Loading