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

Alerts #202

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
17 changes: 2 additions & 15 deletions src/actions/ansible-repository-collection-version-add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ import {
CollectionVersionAPI,
type CollectionVersionSearch,
} from 'src/api';
import {
AlertList,
type AlertType,
DetailList,
closeAlert,
} from 'src/components';
import { DetailList, useAddAlert } from 'src/components';
import { handleHttpError, parsePulpIDFromURL, taskAlert } from 'src/utilities';
import { Action } from './action';

Expand Down Expand Up @@ -86,12 +81,9 @@ const AddCollectionVersionModal = ({
closeAction: () => void;
sourceRepository: AnsibleRepositoryType;
}) => {
const [alerts, setAlerts] = useState([]);
const [selected, setSelected] = useState<CollectionVersionSearch[]>([]);

const addAlert = (alert: AlertType) => {
setAlerts([...alerts, alert]);
};
const addAlert = useAddAlert();

// @ts-expect-error: TS2525: Initializer provides no value for this binding element and the binding element has no default value.
const query = ({ params } = {}) => CollectionVersionAPI.list(params);
Expand Down Expand Up @@ -209,11 +201,6 @@ const AddCollectionVersionModal = ({
title={t`Collection versions`}
/>
</section>

<AlertList
alerts={alerts}
closeAlert={(i) => closeAlert(i, { alerts, setAlerts })}
/>
</Modal>
);
};
Expand Down
94 changes: 94 additions & 0 deletions src/components/alerts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { AlertActionCloseButton, AlertVariant } from '@patternfly/react-core';
import { type ReactNode, createContext, useContext, useState } from 'react';
import { Alert } from 'src/components';

export interface AlertType {
key?: number;
id?: string;
variant: 'danger' | 'success' | 'warning' | 'info' | 'custom' | AlertVariant;
title: string | ReactNode;
description?: string | ReactNode;
}

interface IAlertsContextType {
alerts: AlertType[];
addAlert: (alert: AlertType) => void;
closeAlert: (id: number) => void;
}

// Do not export this to keep alerts and closeAlert private.
const AlertsContext = createContext<IAlertsContextType>(undefined);

// Provide an addAlert method.
// addAlert will add info alerts with 5s timeout.
// If an alert has a 'id' it will replace existing ones with the same 'id'.
export const useAddAlert = () => useContext(AlertsContext).addAlert;

export const AlertsContextProvider = ({
children,
}: {
children: ReactNode;
}) => {
const [{ alerts }, setAlertState] = useState<{
counter: number;
alerts: AlertType[];
}>({ counter: 0, alerts: [] });

const addAlert = (alert: AlertType) =>
setAlertState(({ counter, alerts }) => {
if (alert.variant === AlertVariant.info) {
setTimeout(() => closeAlert(counter), 5000);
}
return {
counter: counter + 1,
alerts: [
...alerts.filter((item) => alert.id === null || item.id != alert.id),
{ ...alert, key: counter },
],
};
});

const closeAlert = (key: number) =>
setAlertState(({ counter, alerts }) => ({
counter,
alerts: alerts.filter((item) => item.key !== key),
}));

return (
<AlertsContext.Provider value={{ alerts, addAlert, closeAlert }}>
{children}
</AlertsContext.Provider>
);
};

export const AlertList = () => {
const { alerts, closeAlert } = useContext(AlertsContext);

return (
<div
style={{
position: 'fixed',
right: '5px',
top: '80px', // 76 + 4
zIndex: 300,
display: 'flex',
flexDirection: 'column',
}}
data-cy='AlertList'
>
{alerts.map(({ key, title, variant, description }) => (
<Alert
style={{ marginBottom: '16px' }}
key={key}
title={title}
variant={variant}
actionClose={
<AlertActionCloseButton onClose={() => closeAlert(key)} />
}
>
{description}
</Alert>
))}
</div>
);
};
47 changes: 9 additions & 38 deletions src/components/collection-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import {
import { useAppContext } from 'src/app-context';
import {
Alert,
AlertList,
type AlertType,
BaseHeader,
type BreadcrumbType,
Breadcrumbs,
Expand All @@ -50,8 +48,9 @@ import {
SignatureBadge,
Spinner,
UploadSignatureModal,
closeAlert,
useAddAlert,
} from 'src/components';
import { type AlertType } from 'src/components/alerts';
import { Paths, formatPath } from 'src/paths';
import {
DeleteCollectionUtils,
Expand Down Expand Up @@ -91,7 +90,6 @@ export const CollectionHeader = ({
reload,
updateParams,
}: IProps) => {
const [alerts, setAlerts] = useState([]);
const [collectionVersion, setCollectionVersion] = useState(null);
const [confirmDelete, setConfirmDelete] = useState(false);
const [copyCollectionToRepositoryModal, setCopyCollectionToRepositoryModal] =
Expand All @@ -117,11 +115,12 @@ export const CollectionHeader = ({
useState(false);
const [versionToUploadCertificate, setVersionToUploadCertificate] =
useState(undefined);
const addAlert = useAddAlert();

useEffect(() => {
DeleteCollectionUtils.countUsedbyDependencies(collection)
.then((count) => setDeletionBlocked(!!count))
.catch((alert) => addAlert(alert));
.catch(addAlert);

NamespaceAPI.get(collection.collection_version.namespace, {
include_related: 'my_permissions',
Expand All @@ -139,7 +138,6 @@ export const CollectionHeader = ({
const context = useAppContext();
const {
featureFlags: { can_upload_signatures, display_signatures },
queueAlert,
settings: { GALAXY_COLLECTION_SIGNING_SERVICE },
} = context;

Expand Down Expand Up @@ -313,7 +311,7 @@ export const CollectionHeader = ({
redirect: formatPath(Paths.ansible.namespace.detail, {
namespace: deleteCollection.collection_version.namespace,
}),
addAlert: (alert) => queueAlert(alert),
addAlert,
deleteFromRepo,
});
}
Expand All @@ -322,7 +320,7 @@ export const CollectionHeader = ({
/>
{copyCollectionToRepositoryModal && (
<CopyCollectionToRepositoryModal
addAlert={(alert) => addAlert(alert)}
addAlert={addAlert}
closeAction={() => setCopyCollectionToRepositoryModal(null)}
collectionVersion={collection}
/>
Expand Down Expand Up @@ -464,15 +462,6 @@ export const CollectionHeader = ({
title={t`This collection has been deprecated.`}
/>
)}
<AlertList
alerts={alerts}
closeAlert={(i) =>
closeAlert(i, {
alerts,
setAlerts,
})
}
/>
<div className='pulp-tab-link-container'>
<div className='tabs'>{renderTabs(activeTab)}</div>
<div className='links'>
Expand Down Expand Up @@ -604,20 +593,16 @@ export const CollectionHeader = ({
if (reload) {
reload();
}
setAlerts((alerts) =>
alerts.filter(({ id }) => id !== 'upload-certificate'),
);
addAlert({
id: 'upload-certificate',
variant: 'success',
title: t`Certificate for collection "${version.namespace} ${version.name} v${version.version}" has been successfully uploaded.`,
});
});
})
.catch((error) => {
setAlerts((alerts) =>
alerts.filter(({ id }) => id !== 'upload-certificate'),
);
addAlert({
id: 'upload-certificate',
variant: 'danger',
title: t`The certificate for "${version.namespace} ${version.name} v${version.version}" could not be saved.`,
description: error,
Expand Down Expand Up @@ -685,11 +670,6 @@ export const CollectionHeader = ({
waitForTask(result.data.task_id)
.then(() => updateParams({}))
.catch((error) => addAlert(errorAlert(error)))
.finally(() =>
setAlerts((alerts) =>
alerts.filter(({ id }) => id !== 'loading-signing'),
),
);
})
.catch((error) =>
// The request failed in the first place
Expand Down Expand Up @@ -725,11 +705,6 @@ export const CollectionHeader = ({
waitForTask(result.data.task_id)
.then(() => updateParams({}))
.catch((error) => addAlert(errorAlert(error)))
.finally(() =>
setAlerts((alerts) =>
alerts.filter(({ id }) => id !== 'loading-signing'),
),
);
})
.catch((error) =>
// The request failed in the first place
Expand Down Expand Up @@ -815,7 +790,7 @@ export const CollectionHeader = ({
});
} else {
// last version in collection => collection will be deleted => redirect
queueAlert({
addAlert({
variant: 'success',
title: t`Collection "${name} v${collectionVersion}" has been successfully deleted.`,
});
Expand Down Expand Up @@ -890,8 +865,4 @@ export const CollectionHeader = ({
function copyToRepository(collection: CollectionVersionSearch) {
setCopyCollectionToRepositoryModal(collection);
}

function addAlert(alert: AlertType) {
setAlerts((alerts) => [...alerts, alert]);
}
};
40 changes: 15 additions & 25 deletions src/components/container-repository-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ import {
ExecutionEnvironmentRemoteAPI,
} from 'src/api';
import {
AlertList,
type AlertType,
FormFieldHelper,
HelpButton,
LabelGroup,
Spinner,
Typeahead,
closeAlert,
useAddAlert,
} from 'src/components';
import { type AlertType } from 'src/components/alerts';
import {
type ErrorMessagesType,
alertErrorsWithoutFields,
Expand All @@ -53,13 +52,15 @@ interface IProps {
registry?: string; // pk
upstreamName?: string;
remoteId?: string;
addAlert?: (variant, title, description?) => void;
}

interface IPropsImpl extends IProps {
addAlert: (alert: AlertType) => void;
}

interface IState {
name: string;
description: string;
alerts: AlertType[];
addTagsInclude: string;
addTagsExclude: string;
excludeTags?: string[];
Expand All @@ -70,7 +71,13 @@ interface IState {
formErrors: ErrorMessagesType;
}

export class ContainerRepositoryForm extends Component<IProps, IState> {
export const ContainerRepositoryForm = (props: IProps) => {
const addAlert = useAddAlert();

return <ContainerRepositoryFormImpl {...props} addAlert={addAlert} />;
};

class ContainerRepositoryFormImpl extends Component<IPropsImpl, IState> {
constructor(props) {
super(props);
this.state = {
Expand All @@ -85,7 +92,6 @@ export class ContainerRepositoryForm extends Component<IProps, IState> {
registrySelection: [],
upstreamName: this.props.upstreamName || '',
formErrors: {},
alerts: [],
};
}

Expand All @@ -105,7 +111,7 @@ export class ContainerRepositoryForm extends Component<IProps, IState> {
.catch((e) => {
const { status, statusText } = e.response;
const errorTitle = t`Registries list could not be displayed.`;
this.addAlert({
this.props.addAlert({
variant: 'danger',
title: errorTitle,
description: jsxErrorMessage(status, statusText),
Expand All @@ -122,7 +128,6 @@ export class ContainerRepositoryForm extends Component<IProps, IState> {
const {
addTagsExclude,
addTagsInclude,
alerts,
description,
excludeTags,
formErrors,
Expand Down Expand Up @@ -153,15 +158,6 @@ export class ContainerRepositoryForm extends Component<IProps, IState> {
</Button>,
]}
>
<AlertList
alerts={alerts}
closeAlert={(i) =>
closeAlert(i, {
alerts,
setAlerts: (alerts) => this.setState({ alerts }),
})
}
/>
<Form>
{!isRemote ? (
<>
Expand Down Expand Up @@ -518,17 +514,11 @@ export class ContainerRepositoryForm extends Component<IProps, IState> {
alertErrorsWithoutFields(
this.state.formErrors,
['name', 'registry', 'registries'],
(alert) => this.addAlert(alert),
this.props.addAlert,
t`Error when saving registry.`,
(state) => this.setState({ formErrors: state }),
);
return Promise.reject(new Error(e));
});
}

private addAlert(alert) {
this.setState({
alerts: [...this.state.alerts, alert],
});
}
}
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { useAddAlert } from './alerts';
export { AccessTab } from './access-tab';
export { AlertList, type AlertType, closeAlert } from './alert-list';
export { AppliedFilters } from './applied-filters';
Expand Down
Loading
Loading