Skip to content

Commit 19575bb

Browse files
peppermint-juliCopilotCopilot
authored
Web/UI fixes (#99)
* refactor: replace MUI Typography with HTML headings and improve job detail layout * Update components/ui/web/components/content/jobs/detail/jobFullDetail.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * started working on rerun with edit capabilities * fix: add JobStatus import and improve summary retrieval * feat: enhance job rerun functionality and improve new compute session options * Code clean up and fixed loading animation backdrop timing issue * Fixed working directory not capturing path to file when user chooses to use own volume * Update loading animation GIF and adjust image component (#98) * Added container ID and JSON tab to display JSON for a container in ContainerDetail view * Added JSON feld to Container type * Add tooltips for action icons in Container and Jobs data grids * Update components/ui/web/components/content/jobs/new/workingDirectoryForm.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update components/ui/web/components/content/jobs/detail/jobFullDetail.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: add stopPropagation to rerun/cancel buttons in job datagrid Agent-Logs-Url: https://github.com/sciserver/opensciserver/sessions/61a677b9-b251-4ef4-a4d4-bb53de6f14aa Co-authored-by: peppermint-juli <27710492+peppermint-juli@users.noreply.github.com> * Update components/ui/web/components/content/compute/sessionManagement/containerDatagrid.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update components/ui/web/components/content/jobs/list/jobDatagrid.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update components/ui/web/components/content/jobs/list/jobDatagrid.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: sanitize rerunFromJobId query parameter in NewJob component * fix: improve readability of onClick handlers in job datagrid * fix: update NEXT_PUBLIC_BASE_PATH environment variable in web deployment * fix: correct BASE_URL format and improve login button URL construction * PR comments * fix: update NEXT_PUBLIC_BASE_PATH to include dynamic prefix * feat: add NEXT_PUBLIC_GLOBUS_CALLBACK_URL to deployment configuration * fix: update Globus callback URL in login component * fix: update NEXT_PUBLIC_GLOBUS_CALLBACK_URL to remove dynamic prefix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent aa4579b commit 19575bb

24 files changed

Lines changed: 424 additions & 152 deletions

File tree

components/ui/graphql/src/data/containers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ export class ContainersAPI extends RESTDataSource {
170170
maxSecs: res.maxSecs,
171171
userVolumes: res.json.userVolumes.map((r: any) => r.userVolumeId),
172172
dataVolumes: res.json.volumeContainers.map((r: any) => this.volumesAPI.computeDataVolumeReducer(r)),
173-
description: res.description
173+
description: res.description,
174+
json: res.json
174175
};
175176
}
176177

components/ui/graphql/src/data/jobs.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { KeyValueCache } from '@apollo/utils.keyvaluecache';
55
import { sortBy } from 'lodash';
66

77
import { environment } from '../environment';
8-
import { CreateJobParams, File, Job, JobDetails, JobFilters, JobMessage } from '../generated/typings';
8+
import { CreateJobParams, File, Job, JobDetails, JobFilters, JobMessage, JobStatus } from '../generated/typings';
99
import { VolumesAPI } from './volumes';
1010

1111
export class JobsAPI extends RESTDataSource {
@@ -45,7 +45,9 @@ export class JobsAPI extends RESTDataSource {
4545

4646
let files: File[] = [];
4747
let summary = 'No summary available';
48-
if (job.resultsFolderURI.length) {
48+
// only attempt to get files and summary if job is successful or failed,
49+
// otherwise the results folder may not exist and we will get an error
50+
if (job.resultsFolderURI.length && (job.status === JobStatus.Success || job.status === JobStatus.Error)) {
4951
const sanitizedURI = job.resultsFolderURI.replace('/home/idies/workspace/', '');
5052
const jobJsontree = await this.volumesAPI.getFilesByVolume(sanitizedURI) || {};
5153
files = jobJsontree.root.files || [];

components/ui/graphql/src/generated/schema.graphql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type Container {
1313
domainName: String!
1414
id: ID!
1515
imageName: String!
16+
json: JSONObject
1617
maxSecs: Int!
1718
name: String!
1819
nodeName: String!

components/ui/graphql/src/generated/typings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type Container = {
3636
domainName: Scalars['String'];
3737
id: Scalars['ID'];
3838
imageName: Scalars['String'];
39+
json?: Maybe<Scalars['JSONObject']>;
3940
maxSecs: Scalars['Int'];
4041
name: Scalars['String'];
4142
nodeName: Scalars['String'];
@@ -567,6 +568,7 @@ export type ContainerResolvers<ContextType = Context, ParentType extends Resolve
567568
domainName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
568569
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
569570
imageName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
571+
json?: Resolver<Maybe<ResolversTypes['JSONObject']>, ParentType, ContextType>;
570572
maxSecs?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
571573
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
572574
nodeName?: Resolver<ResolversTypes['String'], ParentType, ContextType>;

components/ui/graphql/src/types/container.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const typeDefs = gql`
1616
userVolumes: [ID!]!
1717
dataVolumes: [ComputeDataVolume!]!
1818
description: String
19+
json: JSONObject
1920
}
2021
2122
type ContainerDetail {

components/ui/web/.env.development

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
NEXT_PUBLIC_BASE_URL=https://apps.sciserver.org
1+
NEXT_PUBLIC_BASE_URL=https://apps.sciserver.org/
22
NEXT_PUBLIC_FILE_SERVICE_URL=https://apps.sciserver.org/fileservice/api/
33
NEXT_PUBLIC_DASHBOARD_URL=https://apps.sciserver.org/dashboard
44
NEXT_PUBLIC_LOGIN_PORTAL_URL=https://apps.sciserver.org/login-portal/
55
# NEXT_PUBLIC_GRAPHQL_URL=http://localhost:4000/graphql
66
NEXT_PUBLIC_GRAPHQL_URL=https://apps.sciserver.org/graphql
77
NEXT_PUBLIC_NOTEBOOKS_URL=https://apps.sciserver.org/compute/
88
NEXT_PUBLIC_FILES_URL=https://apps.sciserver.org/dashboard/files/uservolumes
9+
NEXT_PUBLIC_GLOBUS_CALLBACK_URL=http://localhost:3000/web/login
910
NEXT_PUBLIC_BASE_PATH='/web'
1011
NEXT_PUBLIC_G_TAG=G-YN6SQW8YNZ
1112
NEXT_PUBLIC_COMPUTE_USER_INACTIVITY_TIMEOUT=2400000 #time in milliseconds
@@ -18,3 +19,4 @@ NEXT_PUBLIC_NEW_JOB_DOMAIN_NAME_DEFAULT=Small Jobs Domain
1819
NEXT_PUBLIC_NEW_JOB_IMAGE_NAME_DEFAULT=SciServer Essentials 4.0
1920
NEXT_PUBLIC_HELPDESK_EMAIL=sciserver-helpdesk@jhu.edu
2021
NEXT_PUBLIC_JOB_WORKSPACE_PATH=/home/idies/workspace/
22+
NEXT_PUBLIC_JOB_URI_CONSTANT_TERMINUS='-2'

components/ui/web/components/common/drawer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ export const DrawerNav: FC = (props: ComponentProps) => {
185185
<div className="drawer-flex">
186186
<List>
187187
{drawerOptions.map((option, index) => (
188-
<>
189-
<ListItem key={option.value} disablePadding>
188+
<div key={option.value}>
189+
<ListItem disablePadding>
190190
<ListItemButton className={menuOption === option.value ? 'selected' : ''} onClick={option.onClick} >
191191
<Tooltip title={drawerOpen ? '' : option.name} >
192192
<ListItemIcon className={menuOption === option.value ? '' : 'contrast'}>
@@ -197,7 +197,7 @@ export const DrawerNav: FC = (props: ComponentProps) => {
197197
</ListItemButton>
198198
</ListItem>
199199
{index === 1 && <Divider sx={{ margin: '10px 0' }} />}
200-
</>
200+
</div>
201201
))}
202202
</List>
203203
{/* // To be update with username initial in upcoming PR where user details are fetched */}

components/ui/web/components/common/loadingAnimation.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Backdrop } from '@mui/material';
44
import styled from 'styled-components';
55

66
import { AppContext } from 'context';
7-
import logoGif from 'public/sciserver-logo.gif';
7+
import logoGif from 'public/sciserver-loading.gif';
88
import { drawerClosedWidth, drawerOpenWidth } from 'components/common/drawer';
99

1010
type Props = {
@@ -27,8 +27,7 @@ export const LoadingAnimation: FC<Props> = ({ backDropIsOpen }) => {
2727
return (
2828
<StyledBackdrop {...{ drawerOpen }} open={backDropIsOpen} >
2929
<div className="loading-div">
30-
<Image src={logoGif} alt="Sciserver logo gif" width={170} />
31-
<div className="loading">Loading...</div>
30+
<Image src={logoGif} alt="Sciserver is loading logo gif" />
3231
</div>
3332
</StyledBackdrop>
3433
);

components/ui/web/components/content/compute/containerRun.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,41 +54,48 @@ const Styled = styled.div`
5454
}
5555
`;
5656

57+
const userInactivityTimeOut = Number.parseInt(process.env.NEXT_PUBLIC_COMPUTE_USER_INACTIVITY_TIMEOUT || '2400000'); // User inactivity 40 minutes
5758

5859
export const ContainerRun: FC = ({ }) => {
5960

6061
const router = useRouter();
61-
6262
const query = router.query;
6363

64-
const [containerId, setContainerId] = useState<string>();
65-
66-
const userInactivityTimeOut = Number.parseInt(process.env.NEXT_PUBLIC_COMPUTE_USER_INACTIVITY_TIMEOUT || '2400000'); // User inactivity 40 minutes
67-
const [pingInterval, setPingInterval] = useState<number | null>(Number.parseInt(process.env.NEXT_PUBLIC_COMPUTE_PING_INTERVAL || '300000')); //Try pinging container every 5 minutes
68-
69-
const [userLastActivity, setUserLastActivity] = useState<number>(Date.now());
70-
64+
// Graphql calls and data
7165
const [getContainerID, { loading: loadingContainerID, data: dataContainerID, error: errorContainerID }] =
7266
useLazyQuery(GET_CONTAINER_ID);
73-
7467
const [pingContainer] = useLazyQuery(PING_CONTAINER, { fetchPolicy: 'network-only', nextFetchPolicy: 'network-only' });
7568

69+
// Context Management
7670
const { token } = useContext(UserContext);
7771
const { drawerOpen, setMenuOption } = useContext(AppContext);
7872

73+
// State Management
74+
const [pingInterval, setPingInterval] = useState<number | null>(Number.parseInt(process.env.NEXT_PUBLIC_COMPUTE_PING_INTERVAL || '300000')); //Try pinging container every 5 minutes
75+
const [containerId, setContainerId] = useState<string>();
76+
const [userLastActivity, setUserLastActivity] = useState<number>(Date.now());
7977
const [path, setPath] = useState<string>('/');
80-
const [backDropIsOpen, setBackDropIsOpen] = useState<boolean>(false);
8178
const [loadingIframe, setLoadingIframe] = useState<boolean>(false);
82-
8379
const [iframeWidth, setIframeWidth] = useState<number>(0);
8480
const [iframeHeight, setIframeHeight] = useState<number>(0);
8581

82+
const globalLoading = useMemo(() => {
83+
if (router.isReady) {
84+
return loadingContainerID || loadingIframe;
85+
}
86+
return true;
87+
}, [loadingContainerID, loadingIframe, router]);
8688

8789
const handleWindowResize = () => {
8890
setIframeWidth(window.innerWidth - (drawerOpen ? drawerOpenWidth : drawerClosedWidth));
8991
setIframeHeight(window.innerHeight - appBarHeight);
9092
};
9193

94+
// Listens for drawerOpen state variable and adjusts width when it changes
95+
useEffect(() => {
96+
handleWindowResize();
97+
}, [drawerOpen]);
98+
9299
// ON MOUNT: UI config
93100
useEffect(() => {
94101
window.addEventListener('resize', handleWindowResize);
@@ -97,18 +104,12 @@ export const ContainerRun: FC = ({ }) => {
97104
setIframeHeight(window.innerHeight - appBarHeight);
98105
}, []);
99106

100-
// Listens for drawerOpen state variable and adjusts width when it changes
101-
useEffect(() => {
102-
handleWindowResize();
103-
}, [drawerOpen]);
104-
105107
// ON MOUNT: API calling
106108
useEffect(() => {
107109
if (!router.isReady) {
108110
return;
109111
}
110112

111-
setBackDropIsOpen(true);
112113
let { img, dom, dvs, uvs, p } = query;
113114

114115
img = sanitize(img as string);
@@ -132,8 +133,6 @@ export const ContainerRun: FC = ({ }) => {
132133
}
133134
}
134135
});
135-
136-
137136
}, [router]);
138137

139138
const showReloadModal = () => {
@@ -163,15 +162,15 @@ export const ContainerRun: FC = ({ }) => {
163162

164163
const checkTimeSinceLastActive = async () => {
165164

166-
console.log(`Checking activity @${new Date()}`);
165+
console.info(`Checking activity @${new Date()}`);
167166
const timeSinceLastActivity = Date.now() - userLastActivity;
168167

169168
if (pingInterval && timeSinceLastActivity < pingInterval) {
170-
console.log(`Recent activity detected for container ${containerId}: last active ${timeSinceLastActivity} ms ago, interval ${pingInterval} ms`);
169+
console.info(`Recent activity detected for container ${containerId}: last active ${timeSinceLastActivity} ms ago, interval ${pingInterval} ms`);
171170
pingContainer({ variables: { containerId } });
172171
return;
173172
}
174-
console.log(`No user activity detected in the past ${pingInterval! / 60_000} minutes`);
173+
console.info(`No user activity detected in the past ${pingInterval! / 60_000} minutes`);
175174
if (timeSinceLastActivity >= userInactivityTimeOut) {
176175
// pingInterval set to null to stop interval
177176
setPingInterval(null);
@@ -224,8 +223,8 @@ export const ContainerRun: FC = ({ }) => {
224223
height={iframeHeight}
225224
onLoad={() => setLoadingIframe(false)}
226225
/>
227-
{loadingContainerID || loadingIframe &&
228-
<LoadingAnimation backDropIsOpen={backDropIsOpen} />
226+
{globalLoading &&
227+
<LoadingAnimation backDropIsOpen={globalLoading} />
229228
}
230229
</Styled>;
231230
};

components/ui/web/components/content/compute/sessionManagement/containerDatagrid.tsx

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { DataGrid, GridActionsCellItem, GridColDef, GridRowId, GridRowParams } f
55
import { Delete as DeleteIcon, PlayArrow as PlayArrowIcon } from '@mui/icons-material';
66

77
import { Container } from 'src/graphql/typings';
8-
import { getExpireTime } from 'src/utils/dates';
8+
import { Tooltip } from '@mui/material';
99

1010
const Styled = styled.div`
1111
.grid {
@@ -47,6 +47,7 @@ type Props = {
4747
export const ContainerDataGrid: FC<Props> = ({ containerList, selectContainer }) => {
4848
const router = useRouter();
4949

50+
// TODO: implement delete container mutation and logic
5051
const deleteContainer = useCallback(
5152
(id: GridRowId) => () => {
5253
// Delete logic goes here
@@ -66,16 +67,19 @@ export const ContainerDataGrid: FC<Props> = ({ containerList, selectContainer })
6667
if (userVolumes.length) {
6768
url += `&uvs=${userVolumes.map(uv => uv)}`;
6869
}
69-
7070
router.push(url);
71-
},
72-
[]);
71+
}, [router]);
7372

7473
const columns: GridColDef<Container>[] = [
74+
{
75+
field: 'id',
76+
headerName: 'ID',
77+
width: 100
78+
},
7579
{
7680
field: 'imageName',
7781
headerName: 'Image',
78-
width: 200
82+
flex: 1
7983
},
8084
{
8185
field: 'domainName',
@@ -85,50 +89,57 @@ export const ContainerDataGrid: FC<Props> = ({ containerList, selectContainer })
8589
{
8690
field: 'dataVolumes',
8791
headerName: 'DataVols',
88-
width: 100,
92+
flex: 0.5,
8993
valueGetter: (value, row: Container) => row.dataVolumes.length
9094
},
9195
{
9296
field: 'userVolumes',
9397
headerName: 'UserVols',
94-
width: 100,
98+
flex: 0.5,
9599
valueGetter: (value, row: Container) => row.userVolumes.length
96100
},
97-
{
98-
field: 'expiry',
99-
headerName: 'Expires in',
100-
width: 120,
101-
valueGetter: (value, row: Container) => getExpireTime(new Date(row.createdAt), row.maxSecs)
102-
},
103101
{
104102
field: 'accessedAt',
105103
headerName: 'Last Accessed',
106104
type: 'dateTime',
107-
width: 220,
105+
width: 150,
108106
valueGetter: (value) => new Date(value)
109107
},
110108
{
111109
field: 'createdAt',
112110
headerName: 'Created',
113111
type: 'date',
114-
width: 150,
112+
flex: 0.6,
115113
valueGetter: (value) => new Date(value)
116114
},
117115
{
118116
field: 'actions',
119117
type: 'actions',
120-
width: 100,
118+
flex: 0.8,
121119
getActions: (params) => [
122120
<GridActionsCellItem
123-
icon={<PlayArrowIcon className="run-icon" />}
121+
icon={
122+
<Tooltip title="Run Container">
123+
<PlayArrowIcon className="run-icon" />
124+
</Tooltip>
125+
}
124126
label="Run"
125127
onClick={runContainer(params)}
126128
/>,
127-
<GridActionsCellItem
128-
icon={<DeleteIcon className="delete-icon" />}
129-
label="Delete"
130-
onClick={deleteContainer(params.id)}
131-
/>
129+
// NOTE: Delete action is currently hidden until the delete container
130+
// functionality works end-to-end. The code is left here for reference
131+
// and future implementation. There are permission issues that we haven't
132+
// dealt with yet around deleting containers that need to be resolved before
133+
// this can be implemented.
134+
// <GridActionsCellItem
135+
// icon={
136+
// <Tooltip title="Delete Container">
137+
// <DeleteIcon className="delete-icon" />
138+
// </Tooltip>
139+
// }
140+
// label="Delete"
141+
// onClick={deleteContainer(params.id)}
142+
// />
132143
]
133144
}
134145
];

0 commit comments

Comments
 (0)