Skip to content

Commit

Permalink
feat(spinner): add spinner to action buttons (#640)
Browse files Browse the repository at this point in the history
* feat(spinner): add spinner to target creation button

* feat(spinner): add spinner to target delete button

* feat(spinner): add spinner to rule creation button

* feat(spinner): add spinner to credentials creation button

* feat(spinner): add spinner to certificate upload form

* feat(spinner): add spinner to event template upload

* chore(notifications): remove duplicate notifications

* feat(spinner): add spinner to agent probe template upload

* feat(spinner): add spinner to rule upload modal

* feat(spinner): add spinner to recording creation form

* feat(spinner): add spinner to archive upload modal

* feat(spinner): add spinner to label bulk-edit

* feat(spinner): add spinner to recording table actions

* chore(spinner): clean up error handlers
  • Loading branch information
tthvo authored Nov 17, 2022
1 parent 02fbd94 commit 31e82e7
Show file tree
Hide file tree
Showing 25 changed files with 1,124 additions and 644 deletions.
210 changes: 123 additions & 87 deletions src/app/Agent/AgentProbeTemplates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ import { ProbeTemplate } from '@app/Shared/Services/Api.service';
import { DeleteWarningType } from '@app/Modal/DeleteWarningUtils';
import { DeleteWarningModal } from '@app/Modal/DeleteWarningModal';
import { AboutAgentCard } from './AboutAgentCard';
import { NotificationsContext } from '@app/Notifications/Notifications';
import { LoadingPropsType } from '@app/Shared/ProgressIndicator';

export interface AgentProbeTemplatesProps {
agentDetected: boolean;
Expand All @@ -90,11 +92,7 @@ export const AgentProbeTemplates: React.FunctionComponent<AgentProbeTemplatesPro
const [templates, setTemplates] = React.useState([] as ProbeTemplate[]);
const [filteredTemplates, setFilteredTemplates] = React.useState([] as ProbeTemplate[]);
const [filterText, setFilterText] = React.useState('');
const [modalOpen, setModalOpen] = React.useState(false);
const [uploadFile, setUploadFile] = React.useState(undefined as File | undefined);
const [uploadFilename, setUploadFilename] = React.useState('');
const [uploading, setUploading] = React.useState(false);
const [fileRejected, setFileRejected] = React.useState(false);
const [uploadModalOpen, setUploadModalOpen] = React.useState(false);
const [sortBy, setSortBy] = React.useState({} as ISortBy);
const [isLoading, setIsLoading] = React.useState(false);
const [errorMessage, setErrorMessage] = React.useState('');
Expand Down Expand Up @@ -143,12 +141,6 @@ export const AgentProbeTemplates: React.FunctionComponent<AgentProbeTemplatesPro
[addSubscription, context.api]
);

const handleUploadCancel = React.useCallback(() => {
setUploadFile(undefined);
setUploadFilename('');
setModalOpen(false);
}, [setUploadFile, setUploadFilename, setModalOpen]);

const handleDeleteButton = React.useCallback(
(rowData) => {
if (context.settings.deletionDialogsEnabledFor(DeleteWarningType.DeleteEventTemplates)) {
Expand All @@ -169,10 +161,6 @@ export const AgentProbeTemplates: React.FunctionComponent<AgentProbeTemplatesPro
setWarningModalOpen(false);
}, [setWarningModalOpen]);

const handleFileRejected = React.useCallback(() => {
setFileRejected(true);
}, [setFileRejected]);

const handleSort = React.useCallback(
(event, index, direction) => {
setSortBy({ index, direction });
Expand Down Expand Up @@ -216,42 +204,12 @@ export const AgentProbeTemplates: React.FunctionComponent<AgentProbeTemplatesPro
);

const handleTemplateUpload = React.useCallback(() => {
setModalOpen(true);
}, [setModalOpen]);
setUploadModalOpen(true);
}, [setUploadModalOpen]);

const handleUploadModalClose = React.useCallback(() => {
setModalOpen(false);
}, [setModalOpen]);

const handleFileChange = React.useCallback(
(value, filename) => {
setFileRejected(false);
setUploadFile(value);
setUploadFilename(filename);
},
[setFileRejected, setUploadFile, setUploadFilename]
);

const handleUploadSubmit = React.useCallback(() => {
if (!uploadFile) {
window.console.error('Attempted to submit probe template upload without a file selected');
return;
}
setUploading(true);
addSubscription(
context.api
.addCustomProbeTemplate(uploadFile)
.pipe(first())
.subscribe((success) => {
setUploading(false);
if (success) {
setUploadFile(undefined);
setUploadFilename('');
setModalOpen(false);
}
})
);
}, [uploadFile, setUploading, addSubscription, context.api, setUploadFile, setUploadFilename, setModalOpen]);
setUploadModalOpen(false);
}, [setUploadModalOpen]);

React.useEffect(() => {
refreshTemplates();
Expand Down Expand Up @@ -376,47 +334,125 @@ export const AgentProbeTemplates: React.FunctionComponent<AgentProbeTemplatesPro
</Title>
</EmptyState>
)}
<Modal
isOpen={modalOpen}
variant={ModalVariant.large}
showClose={true}
onClose={handleUploadModalClose}
title="Create Custom Probe Template"
description="Create a customized probe template. This is a specialized XML file typically created using JDK Mission Control, which defines a set of events to inject and their options to configure."
>
<Form>
<FormGroup
label="Template XML"
isRequired
fieldId="template"
validated={fileRejected ? 'error' : 'default'}
>
<FileUpload
id="probetemplateName"
value={uploadFile}
filename={uploadFilename}
onChange={handleFileChange}
isLoading={uploading}
validated={fileRejected ? 'error' : 'default'}
dropzoneProps={{
accept: '.xml',
onDropRejected: handleFileRejected,
}}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={handleUploadSubmit} isDisabled={!uploadFilename}>
Submit
</Button>
<Button variant="link" onClick={handleUploadCancel}>
Cancel
</Button>
</ActionGroup>
</Form>
</Modal>
<AgentProbeTemplateUploadModal isOpen={uploadModalOpen} onClose={handleUploadModalClose} />
</StackItem>
</Stack>
</>
);
}
};

export interface AgentProbeTemplateUploadModalProps {
isOpen: boolean;
onClose: () => void;
}

export const AgentProbeTemplateUploadModal: React.FunctionComponent<AgentProbeTemplateUploadModalProps> = (props) => {
const [uploadFile, setUploadFile] = React.useState(undefined as File | undefined);
const [uploadFilename, setUploadFilename] = React.useState('');
const [uploading, setUploading] = React.useState(false);
const [fileRejected, setFileRejected] = React.useState(false);
const addSubscription = useSubscriptions();
const context = React.useContext(ServiceContext);
const notifications = React.useContext(NotificationsContext);

const reset = React.useCallback(() => {
setUploadFile(undefined);
setUploadFilename('');
setUploading(false);
setFileRejected(false);
}, [setUploadFile, setUploadFilename, setUploading, setFileRejected]);

const handleFileChange = React.useCallback(
(file, filename) => {
setFileRejected(false);
setUploadFile(file);
setUploadFilename(filename);
},
[setFileRejected, setUploadFile, setUploadFilename]
);

const handleFileRejected = React.useCallback(() => {
setFileRejected(true);
}, [setFileRejected]);

const handleClose = React.useCallback(() => {
reset();
props.onClose();
}, [reset, props.onClose]);

const handleUploadSubmit = React.useCallback(() => {
if (fileRejected) {
notifications.warning('File format is not compatible');
return;
}
if (!uploadFile) {
notifications.warning('Attempted to submit probe template upload without a file selected');
return;
}
setUploading(true);
addSubscription(
context.api
.addCustomProbeTemplate(uploadFile)
.pipe(first())
.subscribe((success) => {
setUploading(false);
if (success) {
handleClose();
}
})
);
}, [fileRejected, uploadFile, setUploading, addSubscription, context.api, handleClose]);

const submitButtonLoadingProps = React.useMemo(
() =>
({
spinnerAriaValueText: 'Submitting',
spinnerAriaLabel: 'submitting-probe-template',
isLoading: uploading,
} as LoadingPropsType),
[uploading]
);

return (
<Modal
isOpen={props.isOpen}
variant={ModalVariant.large}
showClose={!uploading}
onClose={handleClose}
title="Create Custom Probe Template"
description="Create a customized probe template. This is a specialized XML file typically created using JDK Mission Control, which defines a set of events to inject and their options to configure."
>
<Form>
<FormGroup label="Template XML" isRequired fieldId="template" validated={fileRejected ? 'error' : 'default'}>
<FileUpload
id="probetemplateName"
value={uploadFile}
filename={uploadFilename}
onChange={handleFileChange}
isDisabled={uploading}
isLoading={uploading}
validated={fileRejected ? 'error' : 'default'}
dropzoneProps={{
accept: '.xml',
onDropRejected: handleFileRejected,
}}
/>
</FormGroup>
<ActionGroup>
<Button
variant="primary"
onClick={handleUploadSubmit}
isDisabled={!uploadFilename || uploading}
{...submitButtonLoadingProps}
>
Submit
</Button>
<Button variant="link" onClick={handleClose} isDisabled={uploading}>
Cancel
</Button>
</ActionGroup>
</Form>
</Modal>
);
};
53 changes: 37 additions & 16 deletions src/app/AppLayout/JmxAuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import * as React from 'react';
import { ActionGroup, Button, Form, FormGroup, TextInput } from '@patternfly/react-core';
import { ServiceContext } from '@app/Shared/Services/Services';
import { LoadingPropsType } from '@app/Shared/ProgressIndicator';

export interface JmxAuthFormProps {
onDismiss: () => void;
Expand All @@ -49,30 +50,43 @@ export const JmxAuthForm: React.FunctionComponent<JmxAuthFormProps> = (props) =>
const context = React.useContext(ServiceContext);
const [username, setUsername] = React.useState('');
const [password, setPassword] = React.useState('');

const clear = React.useCallback(() => {
setUsername('');
setPassword('');
}, [setUsername, setPassword]);
const [loading, setLoading] = React.useState(false);

const handleSave = React.useCallback(() => {
props.onSave(username, password).then(() => {
context.target.setAuthRetry();
});
}, [context, context.target, clear, props.onSave, username, password]);
setLoading(true);
props
.onSave(username, password)
.then(() => {
// Do not set state as form is unmounted after successful submission
context.target.setAuthRetry();
})
.catch((_) => {
setLoading(false);
});
}, [context.target, setLoading, props.onSave, username, password]);

const handleDismiss = React.useCallback(() => {
clear();
// Do not set state as form is unmounted after cancel
props.onDismiss();
}, [clear, props.onDismiss]);
}, [props.onDismiss]);

const handleKeyUp = React.useCallback(
(event: React.KeyboardEvent): void => {
if (event.code === 'Enter') {
if (event.code === 'Enter' && username !== '' && password !== '') {
handleSave();
}
},
[handleSave]
[handleSave, username, password]
);

const saveButtonLoadingProps = React.useMemo(
() =>
({
spinnerAriaValueText: 'Saving',
spinnerAriaLabel: 'saving-jmx-credentials',
isLoading: loading,
} as LoadingPropsType),
[loading]
);

return (
Expand All @@ -81,6 +95,7 @@ export const JmxAuthForm: React.FunctionComponent<JmxAuthFormProps> = (props) =>
<FormGroup isRequired label="Username" fieldId="username">
<TextInput
value={username}
isDisabled={loading}
isRequired
type="text"
id="username"
Expand All @@ -92,6 +107,7 @@ export const JmxAuthForm: React.FunctionComponent<JmxAuthFormProps> = (props) =>
<FormGroup isRequired label="Password" fieldId="password">
<TextInput
value={password}
isDisabled={loading}
isRequired
type="password"
id="password"
Expand All @@ -100,10 +116,15 @@ export const JmxAuthForm: React.FunctionComponent<JmxAuthFormProps> = (props) =>
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={handleSave}>
Save
<Button
variant="primary"
onClick={handleSave}
{...saveButtonLoadingProps}
isDisabled={loading || username === '' || password === ''}
>
{loading ? 'Saving' : 'Save'}
</Button>
<Button variant="secondary" onClick={handleDismiss}>
<Button variant="secondary" onClick={handleDismiss} isDisabled={loading}>
Cancel
</Button>
</ActionGroup>
Expand Down
Loading

0 comments on commit 31e82e7

Please sign in to comment.