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

Jh/disable module #68

Open
wants to merge 22 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2fde840
Added disable module checkbox to UI
jhurst502 Sep 15, 2021
ff55728
Disabled modules will not show on explore page. Title of diabled modu…
jhurst502 Sep 16, 2021
6004fb6
Start button grayed out with tooltip on disable. Added react-tooltip …
jhurst502 Sep 17, 2021
17b4892
Creators should be able to disable only their own modules.
jhurst502 Sep 21, 2021
783afef
Resolved merge conflicts
jhurst502 Oct 30, 2021
2eb6697
Remove code smells
jhurst502 Oct 31, 2021
f42022d
Update src/components/ModuleCard/ModuleCard.tsx
jhurst502 Nov 7, 2021
fbcc4a7
Update src/pages/Explore/Explore.tsx
jhurst502 Nov 7, 2021
75a2804
Update src/pages/ModuleEditor/ModuleEditor.tsx
jhurst502 Nov 7, 2021
e101e08
Update src/util/LoadingButton.tsx
jhurst502 Nov 7, 2021
05cb83a
Added hooks.ts selector and refactored existing useSelector
jhurst502 Nov 7, 2021
c89e7ce
Fixed start button not graying out for disabled modules
jhurst502 Nov 7, 2021
0140951
Resolved merge conflicts
jhurst502 Nov 17, 2021
602ed69
Fixed TSLint issue in module card
jhurst502 Nov 17, 2021
6676b26
Loading button tool tip text is passed as prop
jhurst502 Nov 17, 2021
21a92a7
Merged dev
jhurst502 Dec 28, 2021
1ff45e2
Moved React tooltip into the IconButton
jhurst502 Jan 3, 2022
d55ca99
Renamed toolTipText to disabledToolTipText
jhurst502 Jan 3, 2022
c4204cd
Merged dev
jhurst502 Jan 3, 2022
4a306bc
Merge branch 'dev' into jh/disable-module
jasekiw Apr 10, 2022
a29c6ad
run yarn install after merge
jasekiw Apr 10, 2022
4f4775c
Add default while loading the module
jasekiw Apr 10, 2022
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "^5.1.0",
"react-select": "^3.1.0",
"react-tooltip": "^4.2.21",
"redux": "^4.0.4",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
Expand Down
6 changes: 4 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import * as Sentry from '@sentry/browser';
import Routes from './router/Routes';
import {CapsLockContextProvider} from './components/CapsLockContext/CapsLockContext';
// import LabEditorGUI from './pages/LabEditorGUI/LabEditorGUI';

// Configure Sentry
// Sentry.init({
Expand All @@ -26,9 +27,10 @@ class App extends React.Component {

public render() {
return (
<CapsLockContextProvider>
<CapsLockContextProvider>
<Routes/>
</CapsLockContextProvider>
</CapsLockContextProvider>
// <LabEditorGUI></LabEditorGUI>
);
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ export async function turnOnUserLab(id: number): Promise<string> {
return (await retry.post<string>(`/user-lab/${id}/turn-on`)).data;
}



export async function shutdownVm(id: number): Promise<string> {
return (await api.post<string>(`/virtual-machine/${id}/shutdown`)).data;
}
Expand All @@ -74,7 +72,7 @@ export async function resetVm(id: number): Promise<string> {
}

export async function getModule(id: number) {
return ( await api.get<Module>(`/modules/${id}`)).data;
return ( await api.get<Module>(`/module/${id}`)).data;
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved
}

export async function getUserList() {
Expand Down
23 changes: 21 additions & 2 deletions src/components/LabEnvironment/LabEnvironment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {Button, Col, Container, Dropdown, ButtonGroup, ListGroup, Row, Tab} from
import {Status} from '../../pages/Status/Status';
import {Document, Page, pdfjs} from 'react-pdf';
import {PDFDocumentProxy} from 'pdfjs-dist';
import {getUserLabReadmeUrl, getUserLabTopologyUrl} from '../../api';
import {getPublicModule, getUserLabReadmeUrl, getUserLabTopologyUrl} from '../../api';
import {UserLab} from '../../types/UserLab';
import {LoadingButton} from '../../util/LoadingButton';
import {combineClasses, getRemainingLabTime} from '../../util';
import {ConsoleWindowContainer} from '../ConsoleWindow/ConsoleWindowContainer';
import {VmActionsMenu} from '../VmActionsMenu/VmActionsMenu';
import {VmStatusIndicator} from '../util/VmStatusIndicator/VmStatusIndicator';
import styles from './LabEnvironment.module.scss';
import {Module} from '../../types/Module';

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

Expand All @@ -28,6 +29,7 @@ interface LabEnvironmentState {
readmeLoaded: boolean;
show_vm: boolean;
eventKey: string;
module: Module;
}


Expand All @@ -38,9 +40,25 @@ export class LabEnvironment extends Component<LabEnvironmentProps, LabEnvironmen
readmeLoaded: false,
pageNumber: 1,
show_vm: false,
eventKey: '#topology'
eventKey: '#topology',
module: {
id: 0,
createdAt: '',
description: '',
disabled: false,
name: 'Loading',
published: false,
ownerId: 0,
specialCode: '',
type: 'SingleUser',
updatedAt: ''
}
};

async componentDidMount() {
this.setState({module: await getPublicModule(this.props.userLab.userModuleId)});
}

canGoToPrevPage = () => this.state.pageNumber > 1;
canGoToNextPage = () => this.state.pageNumber < this.state.numPages;

Expand Down Expand Up @@ -71,6 +89,7 @@ export class LabEnvironment extends Component<LabEnvironmentProps, LabEnvironmen
{this.isLabAbleToStart() ?
<LoadingButton
loading={this.props.starting}
disabled={this.state.module.disabled}
label='Start Lab'
onClick={this.props.onStartLab}
/> : <h6 style={{textAlign: 'right'}}>Lab's time remaining: {getRemainingLabTime(this.props.userLab.endDateTime!)}</h6>}
Expand Down
28 changes: 21 additions & 7 deletions src/components/LabListEditor/LabListEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ import {faPlusCircle} from '@fortawesome/free-solid-svg-icons';
import {RoutePaths} from '../../router/RoutePaths';
import {LabItem} from '../../types/editorTypes';
import {deleteLab} from '../../api';
import ReactTooltip from 'react-tooltip';


interface Props {
labs: LabItem[];
prefix: string;
moduleId: number;
disabledModule: boolean;
}
function getNewLabEditorLink(moduleId: number) {
return RoutePaths.EditLab.replace(':moduleId', String(moduleId)).replace(':labId', '');
}

export function LabListEditor({labs, prefix, moduleId}: Props) {
export function LabListEditor({labs, prefix, moduleId, disabledModule}: Props) {
return (
<FieldArray
name={prefix}
Expand All @@ -27,12 +29,24 @@ export function LabListEditor({labs, prefix, moduleId}: Props) {
<Row style={{marginBottom: '1rem'}}>
<Col>Labs</Col>
<Col className='d-flex justify-content-end align-items-center'>
<IconButton
icon={faPlusCircle}
size={'2x'}
link={getNewLabEditorLink(moduleId)}
color={'black'}
/>
{ disabledModule ?
<>
<ReactTooltip place='left' type='dark' effect='solid'/>
<a data-tip='"Disable Module" must be unchecked to add a Lab'>
<IconButton
icon={faPlusCircle}
size={'2x'}
color={'grey'}
/>
</a>
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved
</>
:
<IconButton
icon={faPlusCircle}
size={'2x'}
link={getNewLabEditorLink(moduleId)}
color={'black'}
/>}
</Col>
</Row>
{labs.map((lab,index) =>
Expand Down
7 changes: 5 additions & 2 deletions src/components/ModuleCard/ModuleCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface ModuleCardProps extends ReturnType<typeof mapStateToProps> {

class ModuleCardComponent extends Component<ModuleCardProps > {


render() {
let module: Module = this.props.module as Module;
if (this.props.module['module'] !== undefined) {
Expand All @@ -26,7 +27,10 @@ class ModuleCardComponent extends Component<ModuleCardProps > {
<Card className={Styles.card}>
{/*<Card.Img variant='top' src={TestImage}/>*/}
<Card.Body>
<Card.Title>{module.name}</Card.Title>
{module.disabled
? <Card.Title>{module.name} (disabled)</Card.Title>
: <Card.Title>{module.name}</Card.Title>
}
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved
<Card.Text style={{height: 105, textOverflow: 'ellipsis', overflow: 'hidden'}}>
{module.description.substring(0, 150)}
</Card.Text>
Expand All @@ -39,7 +43,6 @@ class ModuleCardComponent extends Component<ModuleCardProps > {
);
}


getStartButton() {
if (this.props.buttonLink) {
return (
Expand Down
3 changes: 2 additions & 1 deletion src/factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export function makeModuleForm(): ModuleForm {
description: '',
name: '',
published: false,
disabled: false,
specialCode: uuid(),
type: 'SingleUser',
userId: 0
ownerId: 0
};
}

Expand Down
8 changes: 7 additions & 1 deletion src/pages/Explore/Explore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ class Explore extends React.Component<{}, ExploreState> {
async loadModules() {
try {
const modules = await getPublicModules();
this.setState({modules: modules, state: 'success'});
const enabledModules = [];
for (const i of modules) {
if (!i.disabled) {
enabledModules.push(i);
}
}
this.setState({modules: enabledModules, state: 'success'});
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved
} catch (_) {
this.setState({state: 'error'});
}
Expand Down
21 changes: 20 additions & 1 deletion src/pages/ModuleEditor/ModuleEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { LinkContainer } from 'react-router-bootstrap';
import CheckBoxInput from '../../components/util/CheckBoxInput/CheckBoxInput';
import {LabListEditor} from '../../components/LabListEditor/LabListEditor';
import {PageTitle} from '../../components/util/PageTitle';
import {useSelector} from 'react-redux';

const moduleTypeOptions: DropdownOption<ModuleType>[] = [
{value: 'SingleUser', label: 'Single User'},
Expand All @@ -38,12 +39,16 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
const [message, setMessage] = useMessage();
const [editing, setEditing] = useState(false);
const [redirect, setRedirect] = useState();
const [canDisable, setCanDisable] = useState(false);

function completeLoading() {
setLoading(false);
setMessage(undefined);
}

const userRole = useSelector((state: any) => state.entities.currentUser.role);
const userId = useSelector((state: any) => state.entities.currentUser.id);
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved

const onSubmit = async (form: ModuleForm, {setErrors}: FormikHelpers<ModuleForm>) => {
setMessage(undefined);
try {
Expand Down Expand Up @@ -88,6 +93,15 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
LoadModule();
}, [moduleId]);

useEffect(() => {
if (userRole === 'Admin') {
setCanDisable(true);
}
else if (userRole === 'Creator' && userId === initialValues.ownerId) {
setCanDisable(true);
}
}, [initialValues]);

const renderForm = () => (
<Formik
initialValues={initialValues}
Expand Down Expand Up @@ -125,6 +139,11 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
<Form.Group>
<CheckBoxInput name={propertyOf<ModuleForm>('published')} label='Publish (Display on the explore page)' disabled={!editing}/>
</Form.Group>
{canDisable &&
<Form.Group>
<CheckBoxInput name={propertyOf<ModuleForm>('disabled')} label='Disable Module' disabled={!editing}/>
</Form.Group>
}
<Form.Group>
<Form.Label column={true}>Type</Form.Label>
<DropdownInput name={propertyOf<ModuleForm>('type')} dropdownData={moduleTypeOptions} disabled={!editing}/>
Expand All @@ -138,7 +157,7 @@ export default function ModuleEditor({match: {params: {moduleId}}}: Props) {
Once you save your changes you can add and remove labs from this module. Note: Adding or editing a lab will cancel changes on this page
</p>
{values.id !== 0 &&
<LabListEditor labs={values.labs} prefix={propertyOf<ModuleForm>('labs')} moduleId={values.id}/>
<LabListEditor labs={values.labs} prefix={propertyOf<ModuleForm>('labs')} moduleId={values.id} disabledModule={values.disabled}/>
}
</Col>
</Form>
Expand Down
3 changes: 2 additions & 1 deletion src/pages/ModuleEditor/ModuleEditorSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export const ModuleEditorSchema = object<ModuleForm>({
name: string().min(3, 'Must at least be 3 characters').required('Required'),
specialCode : string().required('Required'),
description: string().required('Required').min(4, 'Required'),
disabled: boolean(),
updatedAt: string().notRequired(),
createdAt: string().notRequired(),
id: number(),
userId: number(),
ownerId: number(),
jhurst502 marked this conversation as resolved.
Show resolved Hide resolved
type: string().oneOf(moduleTypes) as StringSchema<ModuleType>,
published: boolean(),
labs: array()
Expand Down
3 changes: 2 additions & 1 deletion src/pages/PublicModule/PublicModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ class PublicModule extends Component<PublicModuleProps, MyModuleState> {
id: 0,
createdAt: '',
description: '',
disabled: false,
name: 'Loading',
published: false,
userId: 0,
ownerId: 0,
specialCode: '',
type: 'SingleUser',
updatedAt: ''
Expand Down
3 changes: 2 additions & 1 deletion src/pages/UserModulePage/UserModulePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class UserModulePage extends Component <UserModuleLabsProps, UserModuleLabsState
id: 0,
name: '',
description: '',
userId: 0,
disabled: false,
ownerId: 0,
published: false,
updatedAt: '',
createdAt: '',
Expand Down
3 changes: 2 additions & 1 deletion src/types/Module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import {TrackableEntity} from './Entity';
export interface BaseModule extends TrackableEntity {
name: string;
description: string;
disabled: boolean;
published: boolean;
specialCode: string;
type: ModuleType;
userId: number;
ownerId: number;
}

export interface Module extends BaseModule {
Expand Down
1 change: 1 addition & 0 deletions src/types/UserLab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface UserLab extends Entity {
hasReadme: boolean;
endDateTime?: string;
status: UserLabStatus;
userModuleId: number;
}

export type UserLabStatus = 'Started' | 'NotStarted' | 'Completed';
Expand Down
41 changes: 31 additions & 10 deletions src/util/LoadingButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {Button, ButtonProps, Spinner} from 'react-bootstrap';
import {SyntheticEvent} from 'react';
import ReactTooltip from 'react-tooltip';

type onClick = ((e: SyntheticEvent<HTMLButtonElement>) => void) | (() => void) | (() => any);

Expand All @@ -14,14 +15,34 @@ interface Props {
}

export const LoadingButton = (props: Props) => (
<Button disabled={props.loading || props.disabled} variant='primary' type={props.type || 'submit'} className={props.className} onClick={props.onClick}>
{ props.loading ?
<Spinner
as='span'
animation='border'
size='sm'
role='status'
aria-hidden='true'
/> : props.label}
</Button>
<>

{props.disabled
? <>
<ReactTooltip place='left' type='dark' effect='solid'/>
<a data-tip='This lab is currently disabled'>
<Button disabled={true} variant='primary' type={props.type || 'submit'} className={props.className} onClick={props.onClick}>
{ props.loading ?
<Spinner
as='span'
animation='border'
size='sm'
role='status'
aria-hidden='true'
/> : props.label}
</Button>
</a>
</>
: <Button disabled={props.loading} variant='primary' type={props.type || 'submit'} className={props.className} onClick={props.onClick}>
{ props.loading ?
<Spinner
as='span'
animation='border'
size='sm'
role='status'
aria-hidden='true'
/> : props.label}
</Button>
}
</>
);
Loading