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

[RHOAIENG-5491] Landing page: Info section about AI flows #2723

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
106 changes: 98 additions & 8 deletions frontend/src/__tests__/cypress/cypress/e2e/home/home.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@ import { enabledPage } from '~/__tests__/cypress/cypress/pages/enabled';

type HandlersProps = {
disableHome?: boolean;
disableProjects?: boolean;
disableModelServing?: boolean;
disablePipelines?: boolean;
};

const initIntercepts = ({ disableHome }: HandlersProps) => {
cy.interceptOdh(
'GET /api/config',
mockDashboardConfig({
disableHome,
}),
);
const initIntercepts = (config: HandlersProps = {}) => {
const dashboardConfig = {
disableProjects: false,
disableModelServing: false,
disablePipelines: false,
...config,
};
cy.interceptOdh('GET /api/config', mockDashboardConfig(dashboardConfig));
cy.interceptOdh('GET /api/components', { query: { installed: 'true' } }, mockComponents());
};

describe('Home page', () => {
it('should not be shown by default', () => {
initIntercepts({});
initIntercepts();
cy.visit('/');
cy.findByTestId('enabled-application').should('be.visible');
});
Expand All @@ -30,4 +34,90 @@ describe('Home page', () => {
// enabled applications page is still navigable
enabledPage.visit(true);
});
it('should show the appropriate AI flow cards', () => {
initIntercepts({ disableHome: false });
cy.visit('/');

cy.findByTestId('ai-flow-projects-card').should('be.visible');
cy.findByTestId('ai-flow-train-card').should('be.visible');
cy.findByTestId('ai-flow-models-card').should('be.visible');
});
it('should show the appropriate info cards', () => {
initIntercepts({ disableHome: false });
cy.visit('/');

cy.findByTestId('ai-flow-projects-card').click();
cy.findByTestId('ai-flows-projects-info').should('be.visible');
cy.findByTestId('ai-flows-connections-info').should('be.visible');
cy.findByTestId('ai-flows-storage-info').should('be.visible');

cy.findByTestId('ai-flow-train-card').click();
cy.findByTestId('ai-flows-workbenches-info').should('be.visible');
cy.findByTestId('ai-flows-pipelines-info').should('be.visible');
cy.findByTestId('ai-flows-runs-info').should('be.visible');

cy.findByTestId('ai-flow-models-card').click();
cy.findByTestId('ai-flows-model-servers-info').should('be.visible');
cy.findByTestId('ai-flows-model-deploy-info').should('be.visible');
});
it('should close the info cards on re-click', () => {
initIntercepts({ disableHome: false });
cy.visit('/');

cy.findByTestId('ai-flow-projects-card').click();
cy.findByTestId('ai-flows-projects-info').should('be.visible');
cy.findByTestId('ai-flows-connections-info').should('be.visible');
cy.findByTestId('ai-flows-storage-info').should('be.visible');

cy.findByTestId('ai-flow-projects-card').click();
cy.findByTestId('ai-flows-projects-info').should('not.exist');
cy.findByTestId('ai-flows-connections-info').should('not.exist');
cy.findByTestId('ai-flows-storage-info').should('not.exist');
});
it('should close the info cards on close button click', () => {
initIntercepts({ disableHome: false });
cy.visit('/');

cy.findByTestId('ai-flow-projects-card').click();
cy.findByTestId('ai-flows-projects-info').should('be.visible');
cy.findByTestId('ai-flows-connections-info').should('be.visible');
cy.findByTestId('ai-flows-storage-info').should('be.visible');

cy.findByTestId('ai-flows-close-info').click();
cy.findByTestId('ai-flows-projects-info').should('not.exist');
cy.findByTestId('ai-flows-connections-info').should('not.exist');
cy.findByTestId('ai-flows-storage-info').should('not.exist');
});
it('should hide sections that are disabled', () => {
initIntercepts({
disableHome: false,
disableProjects: true,
});
cy.visit('/');
cy.findByTestId('home-page').should('be.visible');

cy.findByTestId('ai-flow-projects-card').should('not.exist');

initIntercepts({
disableHome: false,
disableModelServing: true,
});
cy.visit('/');
cy.findByTestId('home-page').should('be.visible');
cy.findByTestId('ai-flow-models-card').should('not.exist');
});
it('should hide info cards that are disabled', () => {
initIntercepts({
disableHome: false,
disablePipelines: true,
});
cy.visit('/');
cy.findByTestId('home-page').should('be.visible');

cy.findByTestId('ai-flow-train-card').click();

cy.findByTestId('ai-flows-workbenches-info').should('be.visible');
cy.findByTestId('ai-flows-pipelines-info').should('not.exist');
cy.findByTestId('ai-flows-runs-info').should('not.exist');
});
});
14 changes: 12 additions & 2 deletions frontend/src/concepts/design/DividedGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ type DividedGalleryProps = Omit<GalleryProps, 'minWidths' | 'maxWidths'> & {
minSize: string;
itemCount: number;
showClose?: boolean;
closeAlt?: string;
onClose?: () => void;
closeTestId?: string;
};

import './DividedGallery.scss';
Expand All @@ -16,9 +18,11 @@ const DividedGallery: React.FC<DividedGalleryProps> = ({
minSize,
itemCount,
showClose,
closeAlt,
onClose,
children,
className,
closeTestId,
...rest
}) => (
<div className={css('odh-divided-gallery', className)} {...rest}>
Expand All @@ -30,8 +34,14 @@ const DividedGallery: React.FC<DividedGalleryProps> = ({
{children}
{showClose ? (
<div className="odh-divided-gallery__close">
<Button aria-label="close" isInline variant="plain" onClick={onClose}>
<TimesIcon />
<Button
data-testid={closeTestId}
aria-label={closeAlt || 'close'}
isInline
variant="plain"
onClick={onClose}
>
<TimesIcon alt={`close ${closeAlt}`} />
</Button>
</div>
) : null}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/concepts/design/InfoGalleryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ButtonVariant,
Flex,
FlexItem,
GalleryItemProps,
Stack,
StackItem,
Text,
Expand All @@ -22,7 +23,7 @@ type InfoGalleryItemProps = {
description: string;
isOpen: boolean;
onClick?: () => void;
};
} & GalleryItemProps;

const InfoGalleryItem: React.FC<InfoGalleryItemProps> = ({
title,
Expand All @@ -31,8 +32,9 @@ const InfoGalleryItem: React.FC<InfoGalleryItemProps> = ({
description,
isOpen,
onClick,
...rest
}) => (
<DividedGalleryItem>
<DividedGalleryItem {...rest}>
<Stack hasGutter>
<StackItem>
<Flex
Expand Down
63 changes: 62 additions & 1 deletion frontend/src/concepts/design/TypeBorderCard.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
.odh-type-bordered-card {
position: relative;
border-radius: 16px;
border: var(--pf-v5-global--BorderWidth--sm) solid var(--pf-v5-global--BorderColor--100);
border: 1px solid var(--pf-v5-global--BorderColor--100);
padding: 1px;

&:after {
position: absolute;
width: 48px;
Expand Down Expand Up @@ -77,4 +79,63 @@
background: var(--ai-serving--BorderColor);
}
}
&.m-is-selectable {
&:after {
display: none;
top: unset;
bottom: 0;
transform: rotate( -180deg );
}
&:hover {
cursor: pointer;
}
&:hover, &.m-is-selected {
&:after {
display: block
}
&.project {
border-color: var(--ai-project--BorderColor);
}
&.notebook {
border-color: var(--ai-notebook--BorderColor);
}
&.pipeline {
border-color: var(--ai-pipeline--BorderColor);
}
&.pipeline-run {
border-color: var(--ai-pipeline--BorderColor);
}
&.cluster-storage {
border-color: var(--ai-cluster-storage--BorderColor);
}
&.model-server {
border-color: var(--ai-model-server--BorderColor);
}
&.data-connection {
border-color: var(--ai-data-connection--BorderColor);
}
&.user {
border-color: var(--ai-user--BorderColor);
}
&.group {
border-color: var(--ai-group--BorderColor);
}
&.set-up {
border-color: var(--ai-set-up--BorderColor);
}
&.organize {
border-color: var(--ai-organize--BorderColor);
}
&.training {
border-color: var(--ai-training--BorderColor);
}
&.serving {
border-color: var(--ai-serving--BorderColor);
}
}
&.m-is-selected {
border-width: 2px;
padding: 0;
}
}
}
18 changes: 17 additions & 1 deletion frontend/src/concepts/design/TypeBorderedCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,30 @@ import './TypeBorderCard.scss';
type TypeBorderedCardProps = CardProps & {
objectType?: ProjectObjectType;
sectionType?: SectionType;
selectable?: boolean;
selected?: boolean;
};
const TypeBorderedCard: React.FC<TypeBorderedCardProps> = ({
objectType,
sectionType,
className,
selectable,
selected,
...rest
}) => (
<Card className={css(className, 'odh-type-bordered-card', sectionType, objectType)} {...rest} />
<Card
className={css(
className,
'odh-type-bordered-card',
sectionType,
objectType,
selectable && 'm-is-selectable',
selected && 'm-is-selected',
)}
role={selectable ? 'button' : undefined}
aria-expanded={selectable ? selected : undefined}
{...rest}
/>
);

export default TypeBorderedCard;
4 changes: 2 additions & 2 deletions frontend/src/concepts/design/vars.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
:root {
--ai-set-up--BackgroundColor: #ffe8cc;
--ai-organize--BackgroundColor: #fff4cc;
--ai-organize--BackgroundColor: #ffe8cc;
--ai-training--BackgroundColor: #daf2f2;
--ai-serving--BackgroundColor: #e0f0ff;

--ai-set-up--BorderColor: #f8ae54;
--ai-organize--BorderColor: #ffcc17;
--ai-organize--BorderColor: #f8ae54;
--ai-training--BorderColor: #9ad8d8;
--ai-serving--BorderColor: #92c5f9;

Expand Down
4 changes: 4 additions & 0 deletions frontend/src/images/UI_icon-Red_Hat-Branch-Color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions frontend/src/images/UI_icon-Red_Hat-Chart-Color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions frontend/src/images/UI_icon-Red_Hat-Folder-Color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 23 additions & 14 deletions frontend/src/pages/home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,29 @@ import {
PageSectionVariants,
} from '@patternfly/react-core';
import { HomeIcon } from '@patternfly/react-icons';
import { ODH_PRODUCT_NAME } from '~/utilities/const';
import { useAIFlows } from './aiFlows/useAIFlows';

const Home: React.FC = () => (
<PageSection data-testid="home-page" variant={PageSectionVariants.light}>
<Bullseye>
<EmptyState variant="full">
<EmptyStateHeader
titleText="Welcome to the home page"
headingLevel="h4"
icon={<EmptyStateIcon icon={HomeIcon} />}
alt=""
/>
</EmptyState>
</Bullseye>
</PageSection>
);
const Home: React.FC = () => {
const aiFlows = useAIFlows();

if (!aiFlows) {
return (
<PageSection data-testid="home-page-empty" variant={PageSectionVariants.default}>
<Bullseye>
<EmptyState variant="full">
<EmptyStateHeader
titleText={`Welcome to ${ODH_PRODUCT_NAME}`}
headingLevel="h4"
icon={<EmptyStateIcon icon={HomeIcon} />}
/>
</EmptyState>
</Bullseye>
</PageSection>
);
}

return <div data-testid="home-page">{aiFlows}</div>;
};

export default Home;
Loading
Loading