Skip to content

Commit

Permalink
add: connect with upstream api to obtain allocation (#50)
Browse files Browse the repository at this point in the history
* create: upstream autenticator

* add: authentication api

* add: upstream context

* add: login to upstream server too

* add: upstream provider

* add: node-fetch types

* fix: authenticator

* add: hooks and api projects

* fix: add project selector

* fix: remove console

* add: list members

* fix: share and unshare

* add: css link

* fix: css

* add: typing
  • Loading branch information
mosoriob authored Jul 15, 2024
1 parent 102168f commit 2bdca2d
Show file tree
Hide file tree
Showing 29 changed files with 689 additions and 41 deletions.
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"@types/js-cookie": "^2.2.7",
"@types/lodash.clonedeep": "^4.5.6",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.11",
"@types/normalize-path": "^3.0.2",
"@types/react": "^17.0.2",
"@types/react-css-modules": "^4.6.3",
Expand Down
10 changes: 7 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import TapisProvider from 'tapis-hooks/provider';
import 'tapis-ui/index.css';
import { resolveBasePath } from 'utils/resloveBasePath';
import reportWebVitals from './reportWebVitals';
import UpstreamProvider from 'upstream-hooks/provider/UpstreamProvider';
const upstreamBasePath = 'https://upstream-dso.tacc.utexas.edu';

ReactDOM.render(
<React.StrictMode>
<TapisProvider basePath={resolveBasePath()}>
<Router>
<App />
</Router>
<UpstreamProvider basePath={upstreamBasePath}>
<Router>
<App />
</Router>
</UpstreamProvider>
</TapisProvider>
</React.StrictMode>,
document.getElementById('react-root')
Expand Down
18 changes: 18 additions & 0 deletions src/tapis-api/apps/unShare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Apps } from '@tapis/tapis-typescript';
import { apiGenerator, errorDecoder } from 'tapis-api/utils';

const unShareApp = (
request: Apps.UnShareAppRequest,
basePath: string,
jwt: string
) => {
const api: Apps.SharingApi = apiGenerator<Apps.SharingApi>(
Apps,
Apps.SharingApi,
basePath,
jwt
);
return errorDecoder<Apps.RespBasic>(() => api.unShareApp(request));
};

export default unShareApp;
139 changes: 105 additions & 34 deletions src/tapis-app/Apps/_components/Toolbar/ShareModal/ShareModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useCallback, useState } from 'react';
import { Button } from 'reactstrap';
import { Button, Container, FormGroup } from 'reactstrap';
import { DropdownSelector, FormikInput, GenericModal } from 'tapis-ui/_common';
import { SubmitWrapper } from 'tapis-ui/_wrappers';
import { QueryWrapper, SubmitWrapper } from 'tapis-ui/_wrappers';
import { ToolbarModalProps } from '../Toolbar';
import { focusManager } from 'react-query';
import { Column } from 'react-table';
Expand All @@ -18,17 +18,27 @@ import useUnsharePublic from 'tapis-hooks/apps/useUnsharePublic';
import useShare, { ShareUserHookParams } from 'tapis-hooks/apps/useShare';
import { Form, Formik } from 'formik';
import { MuiChipsInput } from 'tapis-ui/_common/MuiChipsInput';
import { useList } from 'upstream-hooks/projects';
import ProjectSelector from './components/ProjectSelector';
import UserSelector from './components/UserSelector';
import { remove } from 'js-cookie';
import unShareApp from 'tapis-api/apps/unShare';
import useUnShare from 'tapis-hooks/apps/useUnShare';

const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
const { selectedApps, unselect } = useAppsSelect();
const { shareAppPublicAsync, reset } = useSharePublic();
const { unShareAppPublicAsync, reset: resetUnshare } = useUnsharePublic();
const { shareAppAsync, reset: resetShare } = useShare();
const { unShareAppAsync, reset: resetUnShare } = useUnShare();
const [isPublishedApp, setIsPublishedApp] = useState(false);
const getAllUsers = selectedApps.map((app) => app.sharedWithUsers);
const [users, setUsers] = useState<Array<string>>(
const [existingUsers, setExistingUsers] = useState<Array<string>>(
getAllUsers.filter(String).flat() as Array<string>
);
const [newUsers, setNewUsers] = useState<Array<string>>([]);
const [usersFromProjects, setUsersFromProjects] = useState<Array<string>>([]);
const [removedUsers, setRemovedUsers] = useState<Array<string>>([]);
useEffect(() => {
reset();
}, [reset]);
Expand All @@ -41,6 +51,10 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
resetShare();
}, [resetShare]);

useEffect(() => {
resetUnShare();
}, [resetUnShare]);

const onComplete = useCallback(() => {
// Calling the focus manager triggers react-query's
// automatic refetch on window focus
Expand All @@ -59,11 +73,11 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
});

const {
run: runUnshare,
state: stateUnshare,
isLoading: isLoadingUnshare,
isSuccess: isSuccessUnshare,
error: errorUnshare,
run: runUnsharePublic,
state: stateUnsharePublic,
isLoading: isLoadingUnsharePublic,
isSuccess: isSuccessUnsharePublic,
error: errorUnsharePublic,
} = useAppsOperations<ShareHookParams, Apps.RespChangeCount>({
fn: unShareAppPublicAsync,
onComplete,
Expand All @@ -80,6 +94,17 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
onComplete,
});

const {
run: unShare,
state: stateUnshare,
isLoading: isLoadingUnshare,
isSuccess: isSuccessUnshare,
error: errorUnshare,
} = useAppsOperations<ShareUserHookParams, Apps.RespChangeCount>({
fn: unShareAppAsync,
onComplete,
});

const onSubmit = useCallback(() => {
const operations: Array<ShareHookParams> = selectedApps.map((app) => ({
id: app.id!,
Expand All @@ -88,20 +113,34 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
runSharePublic(operations);
}
if (!isPublishedApp) {
runUnshare(operations);
runUnsharePublic(operations);
}
if (users.length > 0) {
const newMergedUsers = [...usersFromProjects, ...newUsers];
if (newMergedUsers.length > 0) {
//merge users and usersFromProjects
const mergedUsers = [...existingUsers, ...usersFromProjects, ...newUsers];
const userOperations: Array<ShareUserHookParams> = selectedApps.map(
(app) => ({
id: app.id!,
reqShareUpdate: {
users,
users: mergedUsers,
},
})
);
runShare(userOperations);
}
}, [selectedApps, runSharePublic, runUnshare]);
if (removedUsers.length > 0) {
const userOperations: Array<ShareUserHookParams> = selectedApps.map(
(app) => ({
id: app.id!,
reqShareUpdate: {
users: removedUsers,
},
})
);
unShare(userOperations);
}
}, [selectedApps, runSharePublic, runUnsharePublic]);

const removeApps = useCallback(
(file: Apps.TapisApp) => {
Expand Down Expand Up @@ -142,6 +181,7 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
visibility: 'private',
};

const sharedCounter = newUsers.length + usersFromProjects.length;
return (
<GenericModal
toggle={() => {
Expand All @@ -151,6 +191,15 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
title={`Share apps`}
body={
<div>
<div>
Pending Actions for Selected Apps:
<ul>
<li>Unshare with: {removedUsers.length} users </li>
<li>
Share with: {newUsers.length + usersFromProjects.length} users
</li>
</ul>
</div>
<div className={styles['files-list-container']}>
<AppListingTable
apps={selectedApps}
Expand All @@ -161,34 +210,56 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
</div>
<Formik initialValues={initialValues} onSubmit={onSubmit}>
<Form id="share-form">
<h3> General access </h3>
<DropdownSelector
type={undefined}
onChange={(e: any) => {
const value = e.target.value;
if (value === 'public') {
setIsPublishedApp(true);
}
if (value === 'private') {
setIsPublishedApp(false);
}
}}
>
<option value="private">Private</option>
<option value="public">Public</option>
</DropdownSelector>
<h3> Add users </h3>
<MuiChipsInput value={users} onChange={setUsers} />
<FormGroup>
<h3> General access </h3>
<DropdownSelector
type={undefined}
onChange={(e: any) => {
const value = e.target.value;
if (value === 'public') {
setIsPublishedApp(true);
}
if (value === 'private') {
setIsPublishedApp(false);
}
}}
>
<option value="private">Private</option>
<option value="public">Public</option>
</DropdownSelector>
</FormGroup>
<FormGroup>
<h3> Add new users </h3>
<MuiChipsInput
size="small"
value={newUsers}
onChange={setNewUsers}
/>
</FormGroup>
<FormGroup>
<h3> Add new an allocation </h3>
<ProjectSelector
users={usersFromProjects}
setUsers={setUsersFromProjects}
/>
</FormGroup>
<UserSelector
initialUsers={existingUsers}
removedUsers={removedUsers}
setRemovedUsers={setRemovedUsers}
/>
</Form>
</Formik>
</div>
}
footer={
<SubmitWrapper
isLoading={false}
error={errorSharePublic || errorShare || errorUnshare}
error={errorSharePublic || errorShare || errorUnsharePublic}
success={
isSuccessSharePublic || isSuccessUnshare ? `Visibility changed` : ''
isSuccessSharePublic || isSuccessUnsharePublic
? `Visibility changed`
: ''
}
reverse={true}
>
Expand All @@ -199,8 +270,8 @@ const ShareModel: React.FC<ToolbarModalProps> = ({ toggle }) => {
isSuccessSharePublic ||
isLoadingShare ||
isSuccessShare ||
isLoadingUnshare ||
isSuccessUnshare ||
isLoadingUnsharePublic ||
isSuccessUnsharePublic ||
selectedApps.length === 0
}
aria-label="Submit"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useState } from 'react';
import { Container, FormGroup, Input, Label } from 'reactstrap';
import { QueryWrapper } from 'tapis-ui/_wrappers';
import { Project } from 'upstream-api/projects/types';
import { useList } from 'upstream-hooks/projects';

interface ProjectSelectorProps {
setUsers: (users: Array<string>) => void;
users: Array<string>;
}

const ProjectSelector = ({ setUsers, users }: ProjectSelectorProps) => {
const [selectedProjects, setSelectedProjects] = useState<number[]>([]);
const { data: projects, isLoading, error } = useList();
const handleCheckboxChange = (project: Project) => {
if (selectedProjects.includes(project.id)) {
setSelectedProjects(selectedProjects.filter((p) => p !== project.id));
} else {
setSelectedProjects([...selectedProjects, project.id]);
const usernames = project.members.map((member) => member.username);
setUsers([...users, ...usernames]);
}
};

return (
<QueryWrapper isLoading={isLoading} error={error}>
<FormGroup>
{projects &&
projects?.map((project, index) => (
<FormGroup check key={index}>
<Label check>
<Input
type="checkbox"
checked={selectedProjects.includes(project.id)}
onChange={() => handleCheckboxChange(project)}
/>
{project.title}
</Label>
</FormGroup>
))}
</FormGroup>
</QueryWrapper>
);
};

export default ProjectSelector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.link-button {
background: none;
background-color: none;
color: blue;
border: none;
padding: 0;
font: inherit;
cursor: pointer;
text-decoration: underline;
margin-left: 10px;
}

.link-button:hover {
color: darkblue;
}
Loading

0 comments on commit 2bdca2d

Please sign in to comment.