Skip to content
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
Binary file not shown.
5 changes: 4 additions & 1 deletion public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@
"menuDownload": "Download",
"menuCopy": "Copy to clipboard"
},
"NoManagedControlPlaneBanner": {
"IllustratedBanner": {
"titleMessage": "No ManagedControlPlane",
"subtitleMessage": "Create a ManagedControlPlane to get started",
"helpButton": "Help"
},
"IntelligentBreadcrumbs": {
"homeLabel": "Home"
},
"MCPContext": {
"errorMessage": "An unknown error occurred"
},
"NotInLuigiView": {
"titleMessage": "Opened outside of Hyperspace Portal",
"subtitleMessage": "Looks like this page is not opened inside of the Hyperspace Portal. Contact admins for help."
Expand Down
2 changes: 1 addition & 1 deletion src/components/ControlPlane/FluxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function FluxList() {
if (repoErr || kustomizationErr) {
return (
<IllustratedError
error={repoErr || kustomizationErr}
details={repoErr.message || kustomizationErr.message}
title={t('FluxList.noFluxError')}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/ControlPlane/ManagedResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export function ManagedResources() {
<>
<Title level="H4">{t('ManagedResources.header')}</Title>

{error && <IllustratedError error={error} />}
{error && <IllustratedError details={error.message} />}

{!error && (
<AnalyticalTable
Expand Down
2 changes: 1 addition & 1 deletion src/components/ControlPlane/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function Providers() {
<>
<Title level="H4">{t('Providers.headerProviders')}</Title>

{error && <IllustratedError error={error} />}
{error && <IllustratedError details={error.message} />}

{!error && (
<AnalyticalTable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function ControlPlaneListAllWorkspaces({ projectName }: Props) {
return <Loading />;
}
if (error) {
return <IllustratedError error={error} />;
return <IllustratedError details={error.message} />;
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
import '@ui5/webcomponents-fiori/dist/illustrations/EmptyList.js';
import '@ui5/webcomponents-icons/dist/delete';
import { CopyButton } from '../../Shared/CopyButton.tsx';
import { NoManagedControlPlaneBanner } from '../NoManagedControlPlaneBanner.tsx';
import { ControlPlaneCard } from '../ControlPlaneCard/ControlPlaneCard.tsx';
import {
ListWorkspacesType,
Expand All @@ -35,6 +34,9 @@ import IllustratedError from '../../Shared/IllustratedError.tsx';
import { APIError } from '../../../lib/api/error.ts';
import { useTranslation } from 'react-i18next';
import { YamlViewButton } from '../../Yaml/YamlViewButton.tsx';
import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx';
import { useFrontendConfig } from '../../../context/FrontendConfigContext.tsx';
import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';

interface Props {
projectName: string;
Expand Down Expand Up @@ -62,6 +64,8 @@ export function ControlPlaneListWorkspaceGridTile({
const { trigger } = useApiResourceMutation<DeleteWorkspaceType>(
DeleteWorkspaceResource(projectNamespace, workspaceName),
);

const { links } = useFrontendConfig();
const errorView = createErrorView(cpsError);

function createErrorView(error: APIError) {
Expand All @@ -72,7 +76,7 @@ export function ControlPlaneListWorkspaceGridTile({
title={t(
'ControlPlaneListWorkspaceGridTile.permissionErrorMessage',
)}
subtitleText={t(
details={t(
'ControlPlaneListWorkspaceGridTile.permissionErrorMessageSubtitle',
)}
/>
Expand Down Expand Up @@ -145,20 +149,26 @@ export function ControlPlaneListWorkspaceGridTile({
>
{errorView ? (
errorView
) : controlplanes?.length === 0 ? (
<IllustratedBanner
title={t('IllustratedBanner.titleMessage')}
subtitle={t('IllustratedBanner.subtitleMessage')}
illustrationName={IllustrationMessageType.NoData}
help={{
link: links.COM_PAGE_GETTING_STARTED_MCP,
buttonText: t('IllustratedBanner.helpButton'),
}}
/>
) : (
<Grid defaultSpan="XL4 L4 M7 S12">
{controlplanes?.length === 0 ? (
<NoManagedControlPlaneBanner />
) : (
controlplanes?.map((cp) => (
<ControlPlaneCard
key={`${cp.metadata.name}--${cp.metadata.namespace}`}
controlPlane={cp}
projectName={projectName}
workspace={workspace}
/>
))
)}
{controlplanes?.map((cp) => (
<ControlPlaneCard
key={`${cp.metadata.name}--${cp.metadata.namespace}`}
controlPlane={cp}
projectName={projectName}
workspace={workspace}
/>
))}
</Grid>
)}
</Panel>
Expand Down
34 changes: 0 additions & 34 deletions src/components/ControlPlanes/NoManagedControlPlaneBanner.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/components/Projects/ProjectChooser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function ProjectChooser({ currentProjectName }: Props) {
const navigate = useLuigiNavigate();

if (error) {
return <IllustratedError error={error} />;
return <IllustratedError details={error.message} />;
}

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Projects/ProjectsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export default function ProjectsList() {
[],
);
if (error) {
return <IllustratedError error={error} />;
return <IllustratedError details={error.message} />;
}

return (
Expand Down
23 changes: 8 additions & 15 deletions src/components/Shared/IllustratedError.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { IllustratedMessage } from '@ui5/webcomponents-react';
import '@ui5/webcomponents-fiori/dist/illustrations/SimpleError';
import IllustrationMessageDesign from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageDesign.js';
import { useTranslation } from 'react-i18next';
import { IllustratedBanner } from '../Ui/IllustratedBanner/IllustratedBanner';
import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';

interface Props {
title?: string;
subtitleText?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error?: any;
details?: string;
}

export default function IllustratedError({
title,
subtitleText,
error,
}: Props) {
export default function IllustratedError({ title, details }: Props) {
const { t } = useTranslation();

return (
<IllustratedMessage
name="SimpleError"
design={IllustrationMessageDesign.Spot}
titleText={title ?? t('IllustratedError.titleText')}
subtitleText={error ?? subtitleText ?? t('IllustratedError.subtitleText')}
<IllustratedBanner
illustrationName={IllustrationMessageType.SimpleError}
title={title ?? t('IllustratedError.titleText')}
subtitle={details ?? t('IllustratedError.subtitleText')}
/>
);
}
60 changes: 60 additions & 0 deletions src/components/Ui/IllustratedBanner/IllustratedBanner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
import { IllustratedBanner } from './IllustratedBanner';
import '@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js';

describe('<IllustratedBanner />', () => {
it('renders title and subtitle', () => {
cy.mount(
<IllustratedBanner
title="Test title"
subtitle="Test subtitle"
illustrationName={IllustrationMessageType.NoData}
/>,
);

cy.contains('Test title').should('be.visible');
cy.contains('Test subtitle').should('be.visible');
});

it('renders help button with correct text and icon', () => {
cy.mount(
<IllustratedBanner
title="With Help"
subtitle="Subtitle"
illustrationName={IllustrationMessageType.NoData}
help={{
link: 'https://example.com',
buttonText: 'Need Help?',
}}
/>,
);

cy.get('ui5-button').contains('Need Help?').should('be.visible');
cy.get('ui5-button').should(
'have.attr',
'icon',
'sap-icon://question-mark',
);
});

it('renders a link with correct attributes', () => {
cy.mount(
<IllustratedBanner
title="Click Test"
subtitle="Check link attributes"
illustrationName={IllustrationMessageType.NoData}
help={{
link: 'https://example.com',
buttonText: 'Go',
}}
/>,
);

cy.get('a')
.should('have.attr', 'href', 'https://example.com')
.and('have.attr', 'target', '_blank')
.and('have.attr', 'rel', 'noreferrer');

cy.get('a').contains('Go');
});
});
46 changes: 46 additions & 0 deletions src/components/Ui/IllustratedBanner/IllustratedBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import IllustrationMessageDesign from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageDesign.js';
import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
import { FlexBox, IllustratedMessage, Button } from '@ui5/webcomponents-react';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import '@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js';

type InfoBannerProps = {
title: string;
subtitle: string;
illustrationName: IllustrationMessageType; // e.g. 'NoData', 'SimpleError', etc.
help?: {
link: string;
buttonText: string;
buttonIcon?: string;
};
};

export const IllustratedBanner = ({
title,
subtitle,
illustrationName,
help,
}: InfoBannerProps) => {
return (
<FlexBox direction="Column" alignItems="Center">
<IllustratedMessage
design={IllustrationMessageDesign.Spot}
name={illustrationName}
titleText={title}
subtitleText={subtitle}
/>
{help && (
<a href={help.link} target="_blank" rel="noreferrer">
<Button
design={ButtonDesign.Transparent}
icon={
help.buttonIcon ? help.buttonIcon : 'sap-icon://question-mark'
}
>
{help.buttonText}
</Button>
</a>
)}
</FlexBox>
);
};
2 changes: 1 addition & 1 deletion src/components/Yaml/YamlLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const YamlLoader: FC<YamlViewButtonProps> = ({
const { t } = useTranslation();
if (isLoading) return <Loading />;
if (error) {
return <IllustratedError error={t('common.cannotLoadData')} />;
return <IllustratedError details={t('common.cannotLoadData')} />;
}

return (
Expand Down
8 changes: 3 additions & 5 deletions src/context/AuthProviderOnboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { ReactNode } from 'react';
import { AuthProvider } from 'react-oidc-context';
import { AuthProvider, AuthProviderProps } from 'react-oidc-context';
import { OIDCConfig, useFrontendConfig } from './FrontendConfigContext.tsx';
import { WebStorageStateStore } from "oidc-client-ts";
import { AuthProviderProps } from "react-oidc-context";

import { WebStorageStateStore } from 'oidc-client-ts';

interface AuthProviderOnboardingProps {
children?: ReactNode;
Expand Down Expand Up @@ -33,4 +31,4 @@ function buildAuthProviderConfig(oidcConfig: OIDCConfig) {
},
};
return props;
}
}
8 changes: 4 additions & 4 deletions src/context/FrontendConfigContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ export enum Landscape {
Local = 'LOCAL',
}



interface FrontendConfigContextType extends FrontendConfig {
links: DocLinkCreator;
}

export const FrontendConfigContext =
createContext<FrontendConfigContextType | null>(null);

const fetchPromise = fetch('/frontend-config.json').then((res) => res.json()).then((data) => validateAndCastFrontendConfig(data));
const fetchPromise = fetch('/frontend-config.json')
.then((res) => res.json())
.then((data) => validateAndCastFrontendConfig(data));

interface FrontendConfigProviderProps {
children: ReactNode;
Expand Down Expand Up @@ -72,4 +72,4 @@ function validateAndCastFrontendConfig(config: unknown): FrontendConfig {
} catch (error) {
throw new Error(`Invalid frontend config: ${error}`);
}
}
}
Loading
Loading