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

824 read only role may display unintended error in banner #825

Merged
Merged
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
11 changes: 5 additions & 6 deletions src/components/app-list-item/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Link } from 'react-router-dom';

import { useGetVulnerabilities } from './use-get-vulnerabilities';

import AsyncResource from '../async-resource/simple-async-resource';
import { SimpleAsyncResource } from '../async-resource/simple-async-resource';
import { AppBadge } from '../app-badge';
import {
EnvironmentCardStatus,
Expand Down Expand Up @@ -83,7 +83,6 @@ const AppItemStatus: FunctionComponent<ApplicationSummaryModel> = ({
name,
}) => {
const [state] = useGetVulnerabilities(name);

const vulnerabilities = (state.data ?? []).reduce<VulnerabilitySummaryModel>(
(obj, x) =>
aggregateVulnerabilitySummaries([
Expand Down Expand Up @@ -119,10 +118,10 @@ const AppItemStatus: FunctionComponent<ApplicationSummaryModel> = ({

<div>
<div className="grid grid--gap-x-small grid--auto-columns">
<AsyncResource
<SimpleAsyncResource
asyncState={state}
loading={<></>}
customError={<></>}
loadingContent={false}
errorContent={false}
>
{visibleKeys.some((key) => vulnerabilities[key] > 0) && (
<EnvironmentVulnerabilityIndicator
Expand All @@ -132,7 +131,7 @@ const AppItemStatus: FunctionComponent<ApplicationSummaryModel> = ({
visibleKeys={visibleKeys}
/>
)}
</AsyncResource>
</SimpleAsyncResource>

{(environmentActiveComponents || latestJob) && (
<EnvironmentCardStatus
Expand Down
6 changes: 4 additions & 2 deletions src/components/app-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export const AppList: FunctionComponent<AppListProps> = ({
<div className="grid grid--gap-medium app-list--section">
<SimpleAsyncResource
asyncState={favStatus}
loading={<LoadingCards amount={favourites.length} />}
loadingContent={<LoadingCards amount={favourites.length} />}
>
{favouriteApps.length > 0 ? (
<div className="app-list__list">
Expand All @@ -183,7 +183,9 @@ export const AppList: FunctionComponent<AppListProps> = ({
</Typography>
<SimpleAsyncResource
asyncState={allApps}
loading={<LoadingCards amount={randomPlaceholderCount} />}
loadingContent={
<LoadingCards amount={randomPlaceholderCount} />
}
>
{apps.length > 0 && (
<div className="app-list__list">
Expand Down
157 changes: 85 additions & 72 deletions src/components/async-resource/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import {
hasData,
isLoading,
} from '../../state/subscriptions';
import { isNullOrUndefined } from '../../utils/object';

interface AsyncResourcePropsBase<R extends string, P>
extends SubscriptionObjectState {
failedContent?: ReactNode;
loading?: ReactNode;
loadingContent?: ReactNode;
errorContent?: ReactNode;
resource: R;
resourceParams: P;
}
Expand All @@ -34,82 +35,94 @@ export interface AsyncResourceProps
export interface AsyncResourceStrictProps<K extends ApiResourceKey>
extends AsyncResourcePropsBase<K, ApiResourceParams<K>> {}

const LoadingComponent: FunctionComponent<{
content?: ReactNode;
defaultContent: React.JSX.Element;
}> = ({ content, defaultContent }) =>
// if content is a boolean the intent is either to display or hide the default content
!isNullOrUndefined(content) && content !== true ? (
<>{content !== false && content}</>
) : (
defaultContent
);

export const AsyncResource: FunctionComponent<
PropsWithChildren<AsyncResourceProps>
> = ({
children,
hasData,
isLoading,
error,
failedContent,
loading,
loadingContent = true,
errorContent = true,
resource,
resourceParams,
}) => {
if (!hasData && isLoading) {
return loading ? (
<>{loading}</>
) : (
<span>
<CircularProgress size={16} /> Loading…
</span>
);
} else if (error) {
return failedContent ? (
<>{failedContent}</>
) : (
<Alert type="danger">
<Typography variant="h4" token={{ color: 'currentColor' }}>
That didn't work{' '}
<span role="img" aria-label="Sad">
😞
</span>
</Typography>
<Typography token={{ color: 'currentColor' }}>
Error subscribing to resource <code>{resource}</code>
{resourceParams?.length > 0 && (
<>
{' '}
with parameter{resourceParams.length > 1 ? 's' : ''}{' '}
{resourceParams.map((param, idx) => (
<Fragment key={param}>
<code>{param}</code>
{idx < resourceParams.length - 1 ? ', ' : ''}
</Fragment>
))}
</>
)}
</Typography>
<div>
<Typography variant="caption">Error message:</Typography>
<samp className="word-break">{error}</samp>
</div>
<Typography token={{ color: 'currentColor' }}>
You may want to refresh the page. If the problem persists, get in
touch on our Slack{' '}
<Typography
link
href={externalUrls.slackRadixSupport}
rel="noopener noreferrer"
target="_blank"
>
support channel
}) =>
!hasData && isLoading ? (
<LoadingComponent
content={loadingContent}
defaultContent={
<span>
<CircularProgress size={16} /> Loading…
</span>
}
/>
) : error ? (
<LoadingComponent
content={errorContent}
defaultContent={
<Alert type="danger">
<Typography variant="h4" token={{ color: 'currentColor' }}>
That didn't work{' '}
<span role="img" aria-label="Sad">
😞
</span>
</Typography>
</Typography>
</Alert>
);
} else {
return <>{children}</>;
}
};
<Typography token={{ color: 'currentColor' }}>
Error subscribing to resource <code>{resource}</code>
{resourceParams?.length > 0 && (
<>
{' '}
with parameter{resourceParams.length > 1 ? 's' : ''}{' '}
{resourceParams.map((param, idx) => (
<Fragment key={param}>
<code>{param}</code>
{idx < resourceParams.length - 1 ? ', ' : ''}
</Fragment>
))}
</>
)}
</Typography>
<div>
<Typography variant="caption">Error message:</Typography>
<samp className="word-break">{error}</samp>
</div>
<Typography token={{ color: 'currentColor' }}>
You may want to refresh the page. If the problem persists, get in
touch on our Slack{' '}
<Typography
link
href={externalUrls.slackRadixSupport}
rel="noopener noreferrer"
target="_blank"
>
support channel
</Typography>
</Typography>
</Alert>
}
/>
) : (
<>{children}</>
);

AsyncResource.propTypes = {
children: PropTypes.node,
hasData: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
error: PropTypes.string,
failedContent: PropTypes.node,
loading: PropTypes.node,
loadingContent: PropTypes.node,
errorContent: PropTypes.node,
resource: PropTypes.string.isRequired,
resourceParams: PropTypes.arrayOf(PropTypes.string).isRequired,
};
Expand All @@ -118,16 +131,16 @@ export const AsyncResourceConnected: FunctionComponent<
PropsWithChildren<Omit<AsyncResourceProps, keyof SubscriptionObjectState>>
> = (props) => {
const [AsyncResourceConnected] = useState(() =>
connect<SubscriptionObjectState>(
(
state: RootState,
{ resource, resourceParams }: AsyncResourceStrictProps<ApiResourceKey>
) => ({
error: getError(state, resource, resourceParams),
hasData: hasData(state, resource, resourceParams),
isLoading: isLoading(state, resource, resourceParams),
})
)(AsyncResource)
connect<
SubscriptionObjectState,
{},
AsyncResourceStrictProps<ApiResourceKey>,
RootState
>((state, { resource, resourceParams }) => ({
error: getError(state, resource, resourceParams),
hasData: hasData(state, resource, resourceParams),
isLoading: isLoading(state, resource, resourceParams),
}))(AsyncResource)
);

return (
Expand Down
105 changes: 58 additions & 47 deletions src/components/async-resource/simple-async-resource.tsx
Original file line number Diff line number Diff line change
@@ -1,67 +1,78 @@
import { CircularProgress, Typography } from '@equinor/eds-core-react';
import React, { PropsWithChildren, ReactNode } from 'react';
import React, { FunctionComponent, PropsWithChildren, ReactNode } from 'react';

import { Alert } from '../alert';
import { AsyncState } from '../../effects/effect-types';
import { externalUrls } from '../../externalUrls';
import { RequestState } from '../../state/state-utils/request-states';
import { isNullOrUndefined } from '../../utils/object';

export interface SimpleAsyncResourceProps<T> {
asyncState: AsyncState<T>;
loading?: React.JSX.Element;
customError?: ReactNode;
loadingContent?: ReactNode;
errorContent?: ReactNode;
}

const LoadingComponent: FunctionComponent<{
content?: ReactNode;
defaultContent: React.JSX.Element;
}> = ({ content, defaultContent }) =>
// if content is a boolean the intent is either to display or hide the default content
!isNullOrUndefined(content) && content !== true ? (
<>{content !== false && content}</>
) : (
defaultContent
);

export const SimpleAsyncResource = <T,>({
asyncState,
children,
loading,
customError,
}: PropsWithChildren<SimpleAsyncResourceProps<T>>): React.JSX.Element => {
if (!asyncState || asyncState.status === RequestState.IN_PROGRESS) {
return (
loading || (
loadingContent = true,
errorContent = true,
}: PropsWithChildren<SimpleAsyncResourceProps<T>>): React.JSX.Element =>
!asyncState || asyncState.status === RequestState.IN_PROGRESS ? (
<LoadingComponent
content={loadingContent}
defaultContent={
<span>
<CircularProgress size={16} /> Loading…
</span>
)
);
}

if (asyncState.error) {
return customError ? (
<>{customError}</>
) : (
<Alert type="danger">
<Typography variant="h4">
That didn't work{' '}
<span role="img" aria-label="Sad">
😞
</span>
</Typography>
<div className="grid grid--gap-small">
<div>
<Typography variant="caption">Error message:</Typography>
<samp className="word-break">{asyncState.error}</samp>
</div>
<Typography>
You may want to refresh the page. If the problem persists, get in
touch on our Slack{' '}
<Typography
link
href={externalUrls.slackRadixSupport}
rel="noopener noreferrer"
target="_blank"
>
support channel
</Typography>
}
/>
) : asyncState.error ? (
<LoadingComponent
content={errorContent}
defaultContent={
<Alert type="danger">
<Typography variant="h4">
That didn't work{' '}
<span role="img" aria-label="Sad">
😞
</span>
</Typography>
</div>
</Alert>
);
}

return <>{children}</>;
};
<div className="grid grid--gap-small">
<div>
<Typography variant="caption">Error message:</Typography>
<samp className="word-break">{asyncState.error}</samp>
</div>
<Typography>
You may want to refresh the page. If the problem persists, get in
touch on our Slack{' '}
<Typography
link
href={externalUrls.slackRadixSupport}
rel="noopener noreferrer"
target="_blank"
>
support channel
</Typography>
</Typography>
</div>
</Alert>
}
/>
) : (
<>{children}</>
);

export default SimpleAsyncResource;
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const ScheduledBatchList: FunctionComponent<ScheduledBatchListProps> = ({
<Accordion.Header>
<Accordion.HeaderTitle>
<Typography className="whitespace-nowrap" variant="h4" as="span">
Batches ({sortedData.length ?? '...'})
Batches ({sortedData.length ?? ''})
</Typography>
</Accordion.HeaderTitle>
</Accordion.Header>
Expand Down
Loading